fix(auth): enforce authorization checks across API and Livewire components

- Add authorization checks to API controller endpoints (view, create, update, delete)
- Wrap Livewire component methods with try-catch for consistent error handling
- Add AuthorizesRequests trait to components requiring authorization checks
- Ensure all sensitive operations verify user permissions before execution
- Implement unified error handling with handleError() helper function
This commit is contained in:
Andras Bacsai 2026-02-25 14:20:29 +01:00
parent 12f8f80eb1
commit 86b05b902a
84 changed files with 4046 additions and 1055 deletions

View file

@ -176,6 +176,7 @@ public function show(Request $request)
if (is_null($token)) { if (is_null($token)) {
return response()->json(['message' => 'Cloud provider token not found.'], 404); return response()->json(['message' => 'Cloud provider token not found.'], 404);
} }
$this->authorize('view', $token);
return response()->json($this->removeSensitiveData($token)); return response()->json($this->removeSensitiveData($token));
} }
@ -242,6 +243,7 @@ public function store(Request $request)
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
$this->authorize('create', [CloudProviderToken::class]);
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
@ -386,6 +388,7 @@ public function update(Request $request)
if (! $token) { if (! $token) {
return response()->json(['message' => 'Cloud provider token not found.'], 404); return response()->json(['message' => 'Cloud provider token not found.'], 404);
} }
$this->authorize('update', $token);
$token->update(array_intersect_key($body, array_flip($allowedFields))); $token->update(array_intersect_key($body, array_flip($allowedFields)));
@ -459,6 +462,7 @@ public function destroy(Request $request)
if (! $token) { if (! $token) {
return response()->json(['message' => 'Cloud provider token not found.'], 404); return response()->json(['message' => 'Cloud provider token not found.'], 404);
} }
$this->authorize('delete', $token);
if ($token->hasServers()) { if ($token->hasServers()) {
return response()->json(['message' => 'Cannot delete token that is used by servers.'], 400); return response()->json(['message' => 'Cannot delete token that is used by servers.'], 400);

View file

@ -180,6 +180,7 @@ public function create_github_app(Request $request)
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
$this->authorize('create', [GithubApp::class]);
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return; return $return;
@ -555,6 +556,7 @@ public function update_github_app(Request $request, $github_app_id)
$githubApp = GithubApp::where('id', $github_app_id) $githubApp = GithubApp::where('id', $github_app_id)
->where('team_id', $teamId) ->where('team_id', $teamId)
->firstOrFail(); ->firstOrFail();
$this->authorize('update', $githubApp);
// Define allowed fields for update // Define allowed fields for update
$allowedFields = [ $allowedFields = [
@ -721,6 +723,7 @@ public function delete_github_app($github_app_id)
$githubApp = GithubApp::where('id', $github_app_id) $githubApp = GithubApp::where('id', $github_app_id)
->where('team_id', $teamId) ->where('team_id', $teamId)
->firstOrFail(); ->firstOrFail();
$this->authorize('delete', $githubApp);
// Check if the GitHub app is being used by any applications // Check if the GitHub app is being used by any applications
if ($githubApp->applications->isNotEmpty()) { if ($githubApp->applications->isNotEmpty()) {

View file

@ -548,6 +548,7 @@ public function createServer(Request $request)
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
$this->authorize('create', [Server::class]);
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {

View file

@ -96,6 +96,7 @@ public function project_by_uuid(Request $request)
if (! $project) { if (! $project) {
return response()->json(['message' => 'Project not found.'], 404); return response()->json(['message' => 'Project not found.'], 404);
} }
$this->authorize('view', $project);
$project->load(['environments']); $project->load(['environments']);
@ -232,6 +233,7 @@ public function create_project(Request $request)
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
$this->authorize('create', [Project::class]);
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
@ -378,6 +380,7 @@ public function update_project(Request $request)
if (! $project) { if (! $project) {
return response()->json(['message' => 'Project not found.'], 404); return response()->json(['message' => 'Project not found.'], 404);
} }
$this->authorize('update', $project);
$project->update($request->only($allowedFields)); $project->update($request->only($allowedFields));
@ -455,6 +458,7 @@ public function delete_project(Request $request)
if (! $project) { if (! $project) {
return response()->json(['message' => 'Project not found.'], 404); return response()->json(['message' => 'Project not found.'], 404);
} }
$this->authorize('delete', $project);
if (! $project->isEmpty()) { if (! $project->isEmpty()) {
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400); return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
} }
@ -630,6 +634,7 @@ public function create_environment(Request $request)
if (! $project) { if (! $project) {
return response()->json(['message' => 'Project not found.'], 404); return response()->json(['message' => 'Project not found.'], 404);
} }
$this->authorize('update', $project);
$existingEnvironment = $project->environments()->where('name', $request->name)->first(); $existingEnvironment = $project->environments()->where('name', $request->name)->first();
if ($existingEnvironment) { if ($existingEnvironment) {
@ -717,6 +722,7 @@ public function delete_environment(Request $request)
if (! $environment) { if (! $environment) {
return response()->json(['message' => 'Environment not found.'], 404); return response()->json(['message' => 'Environment not found.'], 404);
} }
$this->authorize('delete', $environment);
if (! $environment->isEmpty()) { if (! $environment->isEmpty()) {
return response()->json(['message' => 'Environment has resources, so it cannot be deleted.'], 400); return response()->json(['message' => 'Environment has resources, so it cannot be deleted.'], 400);

View file

@ -109,6 +109,7 @@ public function key_by_uuid(Request $request)
'message' => 'Private Key not found.', 'message' => 'Private Key not found.',
], 404); ], 404);
} }
$this->authorize('view', $key);
return response()->json($this->removeSensitiveData($key)); return response()->json($this->removeSensitiveData($key));
} }
@ -175,6 +176,7 @@ public function create_key(Request $request)
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
$this->authorize('create', [PrivateKey::class]);
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
return $return; return $return;
@ -330,6 +332,7 @@ public function update_key(Request $request)
'message' => 'Private Key not found.', 'message' => 'Private Key not found.',
], 404); ], 404);
} }
$this->authorize('update', $foundKey);
$foundKey->update($request->all()); $foundKey->update($request->all());
return response()->json(serializeApiResponse([ return response()->json(serializeApiResponse([
@ -406,6 +409,7 @@ public function delete_key(Request $request)
if (is_null($key)) { if (is_null($key)) {
return response()->json(['message' => 'Private Key not found.'], 404); return response()->json(['message' => 'Private Key not found.'], 404);
} }
$this->authorize('delete', $key);
if ($key->isInUse()) { if ($key->isInUse()) {
return response()->json([ return response()->json([

View file

@ -144,6 +144,7 @@ public function server_by_uuid(Request $request)
if (is_null($server)) { if (is_null($server)) {
return response()->json(['message' => 'Server not found.'], 404); return response()->json(['message' => 'Server not found.'], 404);
} }
$this->authorize('view', $server);
if ($with_resources) { if ($with_resources) {
$server['resources'] = $server->definedResources()->map(function ($resource) { $server['resources'] = $server->definedResources()->map(function ($resource) {
$payload = [ $payload = [
@ -464,6 +465,7 @@ public function create_server(Request $request)
if (is_null($teamId)) { if (is_null($teamId)) {
return invalidTokenResponse(); return invalidTokenResponse();
} }
$this->authorize('create', [ModelsServer::class]);
$return = validateIncomingRequest($request); $return = validateIncomingRequest($request);
if ($return instanceof \Illuminate\Http\JsonResponse) { if ($return instanceof \Illuminate\Http\JsonResponse) {
@ -664,6 +666,7 @@ public function update_server(Request $request)
if (! $server) { if (! $server) {
return response()->json(['message' => 'Server not found.'], 404); return response()->json(['message' => 'Server not found.'], 404);
} }
$this->authorize('update', $server);
if ($request->proxy_type) { if ($request->proxy_type) {
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) { $validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
return str($proxyType->value)->lower(); return str($proxyType->value)->lower();
@ -757,6 +760,7 @@ public function delete_server(Request $request)
if (! $server) { if (! $server) {
return response()->json(['message' => 'Server not found.'], 404); return response()->json(['message' => 'Server not found.'], 404);
} }
$this->authorize('delete', $server);
if ($server->definedResources()->count() > 0) { if ($server->definedResources()->count() > 0) {
return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400); return response()->json(['message' => 'Server has resources, so you need to delete them before.'], 400);
} }
@ -835,6 +839,7 @@ public function validate_server(Request $request)
if (! $server) { if (! $server) {
return response()->json(['message' => 'Server not found.'], 404); return response()->json(['message' => 'Server not found.'], 404);
} }
$this->authorize('update', $server);
ValidateServer::dispatch($server); ValidateServer::dispatch($server);
return response()->json(['message' => 'Validation started.'], 201); return response()->json(['message' => 'Validation started.'], 201);

View file

@ -118,6 +118,7 @@ public function team_by_id(Request $request)
if (is_null($team)) { if (is_null($team)) {
return response()->json(['message' => 'Team not found.'], 404); return response()->json(['message' => 'Team not found.'], 404);
} }
$this->authorize('view', $team);
$team = $this->removeSensitiveData($team); $team = $this->removeSensitiveData($team);
return response()->json( return response()->json(
@ -176,6 +177,7 @@ public function members_by_id(Request $request)
if (is_null($team)) { if (is_null($team)) {
return response()->json(['message' => 'Team not found.'], 404); return response()->json(['message' => 'Team not found.'], 404);
} }
$this->authorize('view', $team);
$members = $team->members; $members = $team->members;
$members->makeHidden([ $members->makeHidden([
'pivot', 'pivot',

View file

@ -65,58 +65,66 @@ public function manualCheckStatus()
public function force_deploy_without_cache() public function force_deploy_without_cache()
{ {
$this->authorize('deploy', $this->application); try {
$this->authorize('deploy', $this->application);
$this->deploy(force_rebuild: true); $this->deploy(force_rebuild: true);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function deploy(bool $force_rebuild = false) public function deploy(bool $force_rebuild = false)
{ {
$this->authorize('deploy', $this->application); try {
$this->authorize('deploy', $this->application);
if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) { if ($this->application->build_pack === 'dockercompose' && is_null($this->application->docker_compose_raw)) {
$this->dispatch('error', 'Failed to deploy', 'Please load a Compose file first.'); $this->dispatch('error', 'Failed to deploy', 'Please load a Compose file first.');
return; return;
}
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'To deploy to a Swarm cluster you must set a Docker image name first.');
return;
}
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/build-server">documentation</a>');
return;
}
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
return;
}
$this->setDeploymentUuid();
$result = queue_application_deployment(
application: $this->application,
deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild,
);
if ($result['status'] === 'queue_full') {
$this->dispatch('error', 'Deployment queue full', $result['message']);
return;
}
if ($result['status'] === 'skipped') {
$this->dispatch('error', 'Deployment skipped', $result['message']);
return;
}
return $this->redirectRoute('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_uuid' => $this->parameters['environment_uuid'],
], navigate: false);
} catch (\Throwable $e) {
return handleError($e, $this);
} }
if ($this->application->destination->server->isSwarm() && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'To deploy to a Swarm cluster you must set a Docker image name first.');
return;
}
if (data_get($this->application, 'settings.is_build_server_enabled') && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'To use a build server, you must first set a Docker image.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/build-server">documentation</a>');
return;
}
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
return;
}
$this->setDeploymentUuid();
$result = queue_application_deployment(
application: $this->application,
deployment_uuid: $this->deploymentUuid,
force_rebuild: $force_rebuild,
);
if ($result['status'] === 'queue_full') {
$this->dispatch('error', 'Deployment queue full', $result['message']);
return;
}
if ($result['status'] === 'skipped') {
$this->dispatch('error', 'Deployment skipped', $result['message']);
return;
}
return $this->redirectRoute('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_uuid' => $this->parameters['environment_uuid'],
], navigate: false);
} }
protected function setDeploymentUuid() protected function setDeploymentUuid()
@ -127,45 +135,53 @@ protected function setDeploymentUuid()
public function stop() public function stop()
{ {
$this->authorize('deploy', $this->application); try {
$this->authorize('deploy', $this->application);
$this->dispatch('info', 'Gracefully stopping application.<br/>It could take a while depending on the application.'); $this->dispatch('info', 'Gracefully stopping application.<br/>It could take a while depending on the application.');
StopApplication::dispatch($this->application, false, $this->docker_cleanup); StopApplication::dispatch($this->application, false, $this->docker_cleanup);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function restart() public function restart()
{ {
$this->authorize('deploy', $this->application); try {
$this->authorize('deploy', $this->application);
if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) { if ($this->application->additional_servers->count() > 0 && str($this->application->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>'); $this->dispatch('error', 'Failed to deploy', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
return; return;
}
$this->setDeploymentUuid();
$result = queue_application_deployment(
application: $this->application,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
);
if ($result['status'] === 'queue_full') {
$this->dispatch('error', 'Deployment queue full', $result['message']);
return;
}
if ($result['status'] === 'skipped') {
$this->dispatch('success', 'Deployment skipped', $result['message']);
return;
}
return $this->redirectRoute('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_uuid' => $this->parameters['environment_uuid'],
], navigate: false);
} catch (\Throwable $e) {
return handleError($e, $this);
} }
$this->setDeploymentUuid();
$result = queue_application_deployment(
application: $this->application,
deployment_uuid: $this->deploymentUuid,
restart_only: true,
);
if ($result['status'] === 'queue_full') {
$this->dispatch('error', 'Deployment queue full', $result['message']);
return;
}
if ($result['status'] === 'skipped') {
$this->dispatch('success', 'Deployment skipped', $result['message']);
return;
}
return $this->redirectRoute('project.application.deployment.show', [
'project_uuid' => $this->parameters['project_uuid'],
'application_uuid' => $this->parameters['application_uuid'],
'deployment_uuid' => $this->deploymentUuid,
'environment_uuid' => $this->parameters['environment_uuid'],
], navigate: false);
} }
public function render() public function render()

View file

@ -215,24 +215,31 @@ public function add(int $pull_request_id, ?string $pull_request_html_url = null)
public function force_deploy_without_cache(int $pull_request_id, ?string $pull_request_html_url = null) public function force_deploy_without_cache(int $pull_request_id, ?string $pull_request_html_url = null)
{ {
$this->authorize('deploy', $this->application); try {
$this->authorize('deploy', $this->application);
$this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true); $this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null) public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null)
{ {
$this->authorize('deploy', $this->application); try {
$this->authorize('deploy', $this->application);
$this->add($pull_request_id, $pull_request_html_url); $this->add($pull_request_id, $pull_request_html_url);
$this->deploy($pull_request_id, $pull_request_html_url); $this->deploy($pull_request_id, $pull_request_html_url);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false) public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false)
{ {
$this->authorize('deploy', $this->application);
try { try {
$this->authorize('deploy', $this->application);
$this->setDeploymentUuid(); $this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first(); $found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found && ! is_null($pull_request_html_url)) { if (! $found && ! is_null($pull_request_html_url)) {
@ -291,9 +298,8 @@ private function stopContainers(array $containers, $server)
public function stop(int $pull_request_id) public function stop(int $pull_request_id)
{ {
$this->authorize('deploy', $this->application);
try { try {
$this->authorize('deploy', $this->application);
$server = $this->application->destination->server; $server = $this->application->destination->server;
if ($this->application->destination->server->isSwarm()) { if ($this->application->destination->server->isSwarm()) {

View file

@ -14,9 +14,13 @@ class BackupNow extends Component
public function backupNow() public function backupNow()
{ {
$this->authorize('manageBackups', $this->backup->database); try {
$this->authorize('manageBackups', $this->backup->database);
DatabaseBackupJob::dispatch($this->backup); DatabaseBackupJob::dispatch($this->backup);
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.'); $this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
} }

View file

@ -86,18 +86,26 @@ public function stop()
public function restart() public function restart()
{ {
$this->authorize('manage', $this->database); try {
$this->authorize('manage', $this->database);
$activity = RestartDatabase::run($this->database); $activity = RestartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function start() public function start()
{ {
$this->authorize('manage', $this->database); try {
$this->authorize('manage', $this->database);
$activity = StartDatabase::run($this->database); $activity = StartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class); $this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function render() public function render()

View file

@ -56,22 +56,30 @@ public function setSelectedBackup($backupId, $force = false)
public function setCustomType() public function setCustomType()
{ {
$this->authorize('update', $this->database); try {
$this->authorize('update', $this->database);
$this->database->custom_type = $this->custom_type; $this->database->custom_type = $this->custom_type;
$this->database->save(); $this->database->save();
$this->dispatch('success', 'Database type set.'); $this->dispatch('success', 'Database type set.');
$this->refreshScheduledBackups(); $this->refreshScheduledBackups();
} catch (\Throwable $e) {
handleError($e, $this);
}
} }
public function delete($scheduled_backup_id): void public function delete($scheduled_backup_id): void
{ {
$backup = $this->database->scheduledBackups->find($scheduled_backup_id); try {
$this->authorize('manageBackups', $this->database); $this->authorize('manageBackups', $this->database);
$backup->delete(); $backup = $this->database->scheduledBackups->find($scheduled_backup_id);
$this->dispatch('success', 'Scheduled backup deleted.'); $backup->delete();
$this->refreshScheduledBackups(); $this->dispatch('success', 'Scheduled backup deleted.');
$this->refreshScheduledBackups();
} catch (\Throwable $e) {
handleError($e, $this);
}
} }
public function refreshScheduledBackups(?int $id = null): void public function refreshScheduledBackups(?int $id = null): void

View file

@ -30,18 +30,22 @@ public function mount()
public function delete() public function delete()
{ {
$this->validate([ try {
'environment_id' => 'required|int', $this->validate([
]); 'environment_id' => 'required|int',
$environment = Environment::findOrFail($this->environment_id); ]);
$this->authorize('delete', $environment); $environment = Environment::findOrFail($this->environment_id);
$this->authorize('delete', $environment);
if ($environment->isEmpty()) { if ($environment->isEmpty()) {
$environment->delete(); $environment->delete();
return redirectRoute($this, 'project.show', ['project_uuid' => $this->parameters['project_uuid']]); return redirectRoute($this, 'project.show', ['project_uuid' => $this->parameters['project_uuid']]);
}
return $this->dispatch('error', "<strong>Environment {$environment->name}</strong> has defined resources, please delete them first.");
} catch (\Throwable $e) {
return handleError($e, $this);
} }
return $this->dispatch('error', "<strong>Environment {$environment->name}</strong> has defined resources, please delete them first.");
} }
} }

View file

@ -26,18 +26,22 @@ public function mount()
public function delete() public function delete()
{ {
$this->validate([ try {
'project_id' => 'required|int', $this->validate([
]); 'project_id' => 'required|int',
$project = Project::findOrFail($this->project_id); ]);
$this->authorize('delete', $project); $project = Project::findOrFail($this->project_id);
$this->authorize('delete', $project);
if ($project->isEmpty()) { if ($project->isEmpty()) {
$project->delete(); $project->delete();
return redirectRoute($this, 'project.index'); return redirectRoute($this, 'project.index');
}
return $this->dispatch('error', "<strong>Project {$project->name}</strong> has resources defined, please delete them first.");
} catch (\Throwable $e) {
return handleError($e, $this);
} }
return $this->dispatch('error', "<strong>Project {$project->name}</strong> has resources defined, please delete them first.");
} }
} }

View file

@ -7,12 +7,15 @@
use App\Actions\Service\StopService; use App\Actions\Service\StopService;
use App\Enums\ProcessStatus; use App\Enums\ProcessStatus;
use App\Models\Service; use App\Models\Service;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Livewire\Component; use Livewire\Component;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
class Heading extends Component class Heading extends Component
{ {
use AuthorizesRequests;
public Service $service; public Service $service;
public array $parameters; public array $parameters;
@ -99,13 +102,19 @@ public function checkDeployments()
public function start() public function start()
{ {
$activity = StartService::run($this->service, pullLatestImages: true); try {
$this->dispatch('activityMonitor', $activity->id); $this->authorize('deploy', $this->service);
$activity = StartService::run($this->service, pullLatestImages: true);
$this->dispatch('activityMonitor', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function forceDeploy() public function forceDeploy()
{ {
try { try {
$this->authorize('deploy', $this->service);
$activities = Activity::where('properties->type_uuid', $this->service->uuid) $activities = Activity::where('properties->type_uuid', $this->service->uuid)
->where(function ($q) { ->where(function ($q) {
$q->where('properties->status', ProcessStatus::IN_PROGRESS->value) $q->where('properties->status', ProcessStatus::IN_PROGRESS->value)
@ -117,42 +126,53 @@ public function forceDeploy()
} }
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true); $activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id); $this->dispatch('activityMonitor', $activity->id);
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage()); return handleError($e, $this);
} }
} }
public function stop() public function stop()
{ {
try { try {
$this->authorize('stop', $this->service);
StopService::dispatch($this->service, false, $this->docker_cleanup); StopService::dispatch($this->service, false, $this->docker_cleanup);
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->dispatch('error', $e->getMessage()); return handleError($e, $this);
} }
} }
public function restart() public function restart()
{ {
$this->checkDeployments(); try {
if ($this->isDeploymentProgress) { $this->authorize('deploy', $this->service);
$this->dispatch('error', 'There is a deployment in progress.'); $this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
return; return;
}
$activity = StartService::run($this->service, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);
} }
$activity = StartService::run($this->service, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
} }
public function pullAndRestartEvent() public function pullAndRestartEvent()
{ {
$this->checkDeployments(); try {
if ($this->isDeploymentProgress) { $this->authorize('deploy', $this->service);
$this->dispatch('error', 'There is a deployment in progress.'); $this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
return; return;
}
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);
} }
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
} }
public function render() public function render()

View file

@ -7,12 +7,15 @@
use App\Events\ApplicationStatusChanged; use App\Events\ApplicationStatusChanged;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
class Destination extends Component class Destination extends Component
{ {
use AuthorizesRequests;
public $resource; public $resource;
public Collection $networks; public Collection $networks;
@ -59,6 +62,7 @@ public function loadData()
public function stop($serverId) public function stop($serverId)
{ {
try { try {
$this->authorize('deploy', $this->resource);
$server = Server::ownedByCurrentTeam()->findOrFail($serverId); $server = Server::ownedByCurrentTeam()->findOrFail($serverId);
StopApplicationOneServer::run($this->resource, $server); StopApplicationOneServer::run($this->resource, $server);
$this->refreshServers(); $this->refreshServers();
@ -70,6 +74,7 @@ public function stop($serverId)
public function redeploy(int $network_id, int $server_id) public function redeploy(int $network_id, int $server_id)
{ {
try { try {
$this->authorize('deploy', $this->resource);
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
$this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>'); $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>');
@ -110,15 +115,20 @@ public function redeploy(int $network_id, int $server_id)
public function promote(int $network_id, int $server_id) public function promote(int $network_id, int $server_id)
{ {
$main_destination = $this->resource->destination; try {
$this->resource->update([ $this->authorize('update', $this->resource);
'destination_id' => $network_id, $main_destination = $this->resource->destination;
'destination_type' => StandaloneDocker::class, $this->resource->update([
]); 'destination_id' => $network_id,
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]); 'destination_type' => StandaloneDocker::class,
$this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]); ]);
$this->refreshServers(); $this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
$this->resource->refresh(); $this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]);
$this->refreshServers();
$this->resource->refresh();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function refreshServers() public function refreshServers()
@ -130,13 +140,19 @@ public function refreshServers()
public function addServer(int $network_id, int $server_id) public function addServer(int $network_id, int $server_id)
{ {
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]); try {
$this->dispatch('refresh'); $this->authorize('update', $this->resource);
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]);
$this->dispatch('refresh');
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function removeServer(int $network_id, int $server_id, $password) public function removeServer(int $network_id, int $server_id, $password)
{ {
try { try {
$this->authorize('update', $this->resource);
if (! verifyPasswordConfirmation($password, $this)) { if (! verifyPasswordConfirmation($password, $this)) {
return; return;
} }

View file

@ -70,8 +70,12 @@ class HealthChecks extends Component
public function mount() public function mount()
{ {
$this->authorize('view', $this->resource); try {
$this->syncData(); $this->authorize('view', $this->resource);
$this->syncData();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function syncData(bool $toModel = false): void public function syncData(bool $toModel = false): void

View file

@ -47,224 +47,87 @@ public function toggleVolumeCloning(bool $value)
public function cloneTo($destination_id) public function cloneTo($destination_id)
{ {
$this->authorize('update', $this->resource); try {
$this->authorize('update', $this->resource);
$teamScope = fn ($q) => $q->where('team_id', currentTeam()->id); $teamScope = fn ($q) => $q->where('team_id', currentTeam()->id);
$new_destination = StandaloneDocker::whereHas('server', $teamScope)->find($destination_id); $new_destination = StandaloneDocker::whereHas('server', $teamScope)->find($destination_id);
if (! $new_destination) { if (! $new_destination) {
$new_destination = SwarmDocker::whereHas('server', $teamScope)->find($destination_id); $new_destination = SwarmDocker::whereHas('server', $teamScope)->find($destination_id);
} }
if (! $new_destination) { if (! $new_destination) {
return $this->addError('destination_id', 'Destination not found.'); return $this->addError('destination_id', 'Destination not found.');
} }
$uuid = (string) new Cuid2;
$server = $new_destination->server;
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
$route = route('project.application.configuration', [
'project_uuid' => $this->projectUuid,
'environment_uuid' => $this->environmentUuid,
'application_uuid' => $new_resource->uuid,
]).'#resource-operations';
return redirect()->to($route);
} elseif (
$this->resource->getMorphClass() === \App\Models\StandalonePostgresql::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMongodb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMysql::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneMariadb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
$this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
) {
$uuid = (string) new Cuid2; $uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate([ $server = $new_destination->server;
'id',
'created_at',
'updated_at',
])->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
'status' => 'exited',
'started_at' => null,
'destination_id' => $new_destination->id,
]);
$new_resource->save();
$tags = $this->resource->tags; if ($this->resource->getMorphClass() === \App\Models\Application::class) {
foreach ($tags as $tag) { $new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
$new_resource->tags()->attach($tag->id);
}
$new_resource->persistentStorages()->delete(); $route = route('project.application.configuration', [
$persistentVolumes = $this->resource->persistentStorages()->get(); 'project_uuid' => $this->projectUuid,
foreach ($persistentVolumes as $volume) { 'environment_uuid' => $this->environmentUuid,
$originalName = $volume->name; 'application_uuid' => $new_resource->uuid,
$newName = ''; ]).'#resource-operations';
if (str_starts_with($originalName, 'postgres-data-')) { return redirect()->to($route);
$newName = 'postgres-data-'.$new_resource->uuid; } elseif (
} elseif (str_starts_with($originalName, 'mysql-data-')) { $this->resource->getMorphClass() === \App\Models\StandalonePostgresql::class ||
$newName = 'mysql-data-'.$new_resource->uuid; $this->resource->getMorphClass() === \App\Models\StandaloneMongodb::class ||
} elseif (str_starts_with($originalName, 'redis-data-')) { $this->resource->getMorphClass() === \App\Models\StandaloneMysql::class ||
$newName = 'redis-data-'.$new_resource->uuid; $this->resource->getMorphClass() === \App\Models\StandaloneMariadb::class ||
} elseif (str_starts_with($originalName, 'clickhouse-data-')) { $this->resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
$newName = 'clickhouse-data-'.$new_resource->uuid; $this->resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
} elseif (str_starts_with($originalName, 'mariadb-data-')) { $this->resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
$newName = 'mariadb-data-'.$new_resource->uuid; $this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
} elseif (str_starts_with($originalName, 'mongodb-data-')) { ) {
$newName = 'mongodb-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'keydb-data-')) {
$newName = 'keydb-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'dragonfly-data-')) {
$newName = 'dragonfly-data-'.$new_resource->uuid;
} else {
if (str_starts_with($volume->name, $this->resource->uuid)) {
$newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid);
} else {
$newName = $new_resource->uuid.'-'.$volume->name;
}
}
$newPersistentVolume = $volume->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'name' => $newName,
'resource_id' => $new_resource->id,
]);
$newPersistentVolume->save();
if ($this->cloneVolumeData) {
try {
StopDatabase::dispatch($this->resource);
$sourceVolume = $volume->name;
$targetVolume = $newPersistentVolume->name;
$sourceServer = $this->resource->destination->server;
$targetServer = $new_resource->destination->server;
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
StartDatabase::dispatch($this->resource);
} catch (\Exception $e) {
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
}
}
}
$fileStorages = $this->resource->fileStorages()->get();
foreach ($fileStorages as $storage) {
$newStorage = $storage->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'resource_id' => $new_resource->id,
]);
$newStorage->save();
}
$scheduledBackups = $this->resource->scheduledBackups()->get();
foreach ($scheduledBackups as $backup) {
$uuid = (string) new Cuid2; $uuid = (string) new Cuid2;
$newBackup = $backup->replicate([ $new_resource = $this->resource->replicate([
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->fill([ ])->fill([
'uuid' => $uuid, 'uuid' => $uuid,
'database_id' => $new_resource->id, 'name' => $this->resource->name.'-clone-'.$uuid,
'database_type' => $new_resource->getMorphClass(),
'team_id' => currentTeam()->id,
]);
$newBackup->save();
}
$environmentVaribles = $this->resource->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$payload = [
'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(),
];
$newEnvironmentVariable = $environmentVarible->replicate([
'id',
'created_at',
'updated_at',
])->fill($payload);
$newEnvironmentVariable->save();
}
$route = route('project.database.configuration', [
'project_uuid' => $this->projectUuid,
'environment_uuid' => $this->environmentUuid,
'database_uuid' => $new_resource->uuid,
]).'#resource-operations';
return redirect()->to($route);
} elseif ($this->resource->type() === 'service') {
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
'destination_id' => $new_destination->id,
'destination_type' => $new_destination->getMorphClass(),
'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to a different server to work - but maybe we can drop the column)
]);
$new_resource->save();
$tags = $this->resource->tags;
foreach ($tags as $tag) {
$new_resource->tags()->attach($tag->id);
}
$scheduledTasks = $this->resource->scheduled_tasks()->get();
foreach ($scheduledTasks as $task) {
$newTask = $task->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'uuid' => (string) new Cuid2,
'service_id' => $new_resource->id,
'team_id' => currentTeam()->id,
]);
$newTask->save();
}
$environmentVariables = $this->resource->environment_variables()->get();
foreach ($environmentVariables as $environmentVariable) {
$newEnvironmentVariable = $environmentVariable->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(),
]);
$newEnvironmentVariable->save();
}
foreach ($new_resource->applications() as $application) {
$application->update([
'status' => 'exited', 'status' => 'exited',
'started_at' => null,
'destination_id' => $new_destination->id,
]); ]);
$new_resource->save();
$persistentVolumes = $application->persistentStorages()->get(); $tags = $this->resource->tags;
foreach ($tags as $tag) {
$new_resource->tags()->attach($tag->id);
}
$new_resource->persistentStorages()->delete();
$persistentVolumes = $this->resource->persistentStorages()->get();
foreach ($persistentVolumes as $volume) { foreach ($persistentVolumes as $volume) {
$originalName = $volume->name;
$newName = ''; $newName = '';
if (str_starts_with($volume->name, $volume->resource->uuid)) {
$newName = str($volume->name)->replace($volume->resource->uuid, $application->uuid); if (str_starts_with($originalName, 'postgres-data-')) {
$newName = 'postgres-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'mysql-data-')) {
$newName = 'mysql-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'redis-data-')) {
$newName = 'redis-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'clickhouse-data-')) {
$newName = 'clickhouse-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'mariadb-data-')) {
$newName = 'mariadb-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'mongodb-data-')) {
$newName = 'mongodb-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'keydb-data-')) {
$newName = 'keydb-data-'.$new_resource->uuid;
} elseif (str_starts_with($originalName, 'dragonfly-data-')) {
$newName = 'dragonfly-data-'.$new_resource->uuid;
} else { } else {
$newName = $application->uuid.'-'.str($volume->name)->afterLast('-'); if (str_starts_with($volume->name, $this->resource->uuid)) {
$newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid);
} else {
$newName = $new_resource->uuid.'-'.$volume->name;
}
} }
$newPersistentVolume = $volume->replicate([ $newPersistentVolume = $volume->replicate([
@ -273,79 +136,220 @@ public function cloneTo($destination_id)
'updated_at', 'updated_at',
])->fill([ ])->fill([
'name' => $newName, 'name' => $newName,
'resource_id' => $application->id, 'resource_id' => $new_resource->id,
]); ]);
$newPersistentVolume->save(); $newPersistentVolume->save();
if ($this->cloneVolumeData) { if ($this->cloneVolumeData) {
try { try {
StopService::dispatch($application); StopDatabase::dispatch($this->resource);
$sourceVolume = $volume->name; $sourceVolume = $volume->name;
$targetVolume = $newPersistentVolume->name; $targetVolume = $newPersistentVolume->name;
$sourceServer = $application->service->destination->server; $sourceServer = $this->resource->destination->server;
$targetServer = $new_resource->destination->server; $targetServer = $new_resource->destination->server;
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
StartService::dispatch($application); StartDatabase::dispatch($this->resource);
} catch (\Exception $e) { } catch (\Exception $e) {
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
} }
} }
} }
}
foreach ($new_resource->databases() as $database) { $fileStorages = $this->resource->fileStorages()->get();
$database->update([ foreach ($fileStorages as $storage) {
'status' => 'exited', $newStorage = $storage->replicate([
]);
$persistentVolumes = $database->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
$newName = '';
if (str_starts_with($volume->name, $volume->resource->uuid)) {
$newName = str($volume->name)->replace($volume->resource->uuid, $database->uuid);
} else {
$newName = $database->uuid.'-'.str($volume->name)->afterLast('-');
}
$newPersistentVolume = $volume->replicate([
'id', 'id',
'created_at', 'created_at',
'updated_at', 'updated_at',
])->fill([ ])->fill([
'name' => $newName, 'resource_id' => $new_resource->id,
'resource_id' => $database->id,
]); ]);
$newPersistentVolume->save(); $newStorage->save();
}
if ($this->cloneVolumeData) { $scheduledBackups = $this->resource->scheduledBackups()->get();
try { foreach ($scheduledBackups as $backup) {
StopService::dispatch($database->service); $uuid = (string) new Cuid2;
$sourceVolume = $volume->name; $newBackup = $backup->replicate([
$targetVolume = $newPersistentVolume->name; 'id',
$sourceServer = $database->service->destination->server; 'created_at',
$targetServer = $new_resource->destination->server; 'updated_at',
])->fill([
'uuid' => $uuid,
'database_id' => $new_resource->id,
'database_type' => $new_resource->getMorphClass(),
'team_id' => currentTeam()->id,
]);
$newBackup->save();
}
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); $environmentVaribles = $this->resource->environment_variables()->get();
foreach ($environmentVaribles as $environmentVarible) {
$payload = [
'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(),
];
$newEnvironmentVariable = $environmentVarible->replicate([
'id',
'created_at',
'updated_at',
])->fill($payload);
$newEnvironmentVariable->save();
}
StartService::dispatch($database->service); $route = route('project.database.configuration', [
} catch (\Exception $e) { 'project_uuid' => $this->projectUuid,
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); 'environment_uuid' => $this->environmentUuid,
'database_uuid' => $new_resource->uuid,
]).'#resource-operations';
return redirect()->to($route);
} elseif ($this->resource->type() === 'service') {
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'uuid' => $uuid,
'name' => $this->resource->name.'-clone-'.$uuid,
'destination_id' => $new_destination->id,
'destination_type' => $new_destination->getMorphClass(),
'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to a different server to work - but maybe we can drop the column)
]);
$new_resource->save();
$tags = $this->resource->tags;
foreach ($tags as $tag) {
$new_resource->tags()->attach($tag->id);
}
$scheduledTasks = $this->resource->scheduled_tasks()->get();
foreach ($scheduledTasks as $task) {
$newTask = $task->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'uuid' => (string) new Cuid2,
'service_id' => $new_resource->id,
'team_id' => currentTeam()->id,
]);
$newTask->save();
}
$environmentVariables = $this->resource->environment_variables()->get();
foreach ($environmentVariables as $environmentVariable) {
$newEnvironmentVariable = $environmentVariable->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'resourceable_id' => $new_resource->id,
'resourceable_type' => $new_resource->getMorphClass(),
]);
$newEnvironmentVariable->save();
}
foreach ($new_resource->applications() as $application) {
$application->update([
'status' => 'exited',
]);
$persistentVolumes = $application->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
$newName = '';
if (str_starts_with($volume->name, $volume->resource->uuid)) {
$newName = str($volume->name)->replace($volume->resource->uuid, $application->uuid);
} else {
$newName = $application->uuid.'-'.str($volume->name)->afterLast('-');
}
$newPersistentVolume = $volume->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'name' => $newName,
'resource_id' => $application->id,
]);
$newPersistentVolume->save();
if ($this->cloneVolumeData) {
try {
StopService::dispatch($application);
$sourceVolume = $volume->name;
$targetVolume = $newPersistentVolume->name;
$sourceServer = $application->service->destination->server;
$targetServer = $new_resource->destination->server;
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
StartService::dispatch($application);
} catch (\Exception $e) {
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
}
} }
} }
} }
foreach ($new_resource->databases() as $database) {
$database->update([
'status' => 'exited',
]);
$persistentVolumes = $database->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
$newName = '';
if (str_starts_with($volume->name, $volume->resource->uuid)) {
$newName = str($volume->name)->replace($volume->resource->uuid, $database->uuid);
} else {
$newName = $database->uuid.'-'.str($volume->name)->afterLast('-');
}
$newPersistentVolume = $volume->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'name' => $newName,
'resource_id' => $database->id,
]);
$newPersistentVolume->save();
if ($this->cloneVolumeData) {
try {
StopService::dispatch($database->service);
$sourceVolume = $volume->name;
$targetVolume = $newPersistentVolume->name;
$sourceServer = $database->service->destination->server;
$targetServer = $new_resource->destination->server;
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
StartService::dispatch($database->service);
} catch (\Exception $e) {
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
}
}
}
}
$new_resource->parse();
$route = route('project.service.configuration', [
'project_uuid' => $this->projectUuid,
'environment_uuid' => $this->environmentUuid,
'service_uuid' => $new_resource->uuid,
]).'#resource-operations';
return redirect()->to($route);
} }
} catch (\Throwable $e) {
$new_resource->parse(); return handleError($e, $this);
$route = route('project.service.configuration', [
'project_uuid' => $this->projectUuid,
'environment_uuid' => $this->environmentUuid,
'service_uuid' => $new_resource->uuid,
]).'#resource-operations';
return redirect()->to($route);
} }
} }

View file

@ -23,6 +23,8 @@ class ApiTokens extends Component
public bool $canUseWritePermissions = false; public bool $canUseWritePermissions = false;
public bool $canUseDeployPermissions = false;
public function render() public function render()
{ {
return view('livewire.security.api-tokens'); return view('livewire.security.api-tokens');
@ -33,6 +35,7 @@ public function mount()
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled; $this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
$this->canUseRootPermissions = auth()->user()->can('useRootPermissions', PersonalAccessToken::class); $this->canUseRootPermissions = auth()->user()->can('useRootPermissions', PersonalAccessToken::class);
$this->canUseWritePermissions = auth()->user()->can('useWritePermissions', PersonalAccessToken::class); $this->canUseWritePermissions = auth()->user()->can('useWritePermissions', PersonalAccessToken::class);
$this->canUseDeployPermissions = auth()->user()->can('useDeployPermissions', PersonalAccessToken::class);
$this->getTokens(); $this->getTokens();
} }
@ -60,6 +63,13 @@ public function updatedPermissions($permissionToUpdate)
return; return;
} }
if ($permissionToUpdate == 'deploy' && ! $this->canUseDeployPermissions) {
$this->dispatch('error', 'You do not have permission to use deploy permissions.');
$this->permissions = array_diff($this->permissions, ['deploy']);
return;
}
if ($permissionToUpdate == 'root') { if ($permissionToUpdate == 'root') {
$this->permissions = ['root']; $this->permissions = ['root'];
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) { } elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) {
@ -88,6 +98,10 @@ public function addNewToken()
throw new \Exception('You do not have permission to create tokens with write permissions.'); throw new \Exception('You do not have permission to create tokens with write permissions.');
} }
if (in_array('deploy', $this->permissions) && ! $this->canUseDeployPermissions) {
throw new \Exception('You do not have permission to create tokens with deploy permissions.');
}
$this->validate([ $this->validate([
'description' => 'required|min:3|max:255', 'description' => 'required|min:3|max:255',
]); ]);

View file

@ -20,15 +20,19 @@ class CloudInitScriptForm extends Component
public function mount(?int $scriptId = null) public function mount(?int $scriptId = null)
{ {
if ($scriptId) { try {
$this->scriptId = $scriptId; if ($scriptId) {
$cloudInitScript = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId); $this->scriptId = $scriptId;
$this->authorize('update', $cloudInitScript); $cloudInitScript = CloudInitScript::ownedByCurrentTeam()->findOrFail($scriptId);
$this->authorize('update', $cloudInitScript);
$this->name = $cloudInitScript->name; $this->name = $cloudInitScript->name;
$this->script = $cloudInitScript->script; $this->script = $cloudInitScript->script;
} else { } else {
$this->authorize('create', CloudInitScript::class); $this->authorize('create', CloudInitScript::class);
}
} catch (\Throwable $e) {
return handleError($e, $this);
} }
} }

View file

@ -21,7 +21,11 @@ class CloudProviderTokenForm extends Component
public function mount() public function mount()
{ {
$this->authorize('create', CloudProviderToken::class); try {
$this->authorize('create', CloudProviderToken::class);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
protected function rules(): array protected function rules(): array

View file

@ -14,8 +14,12 @@ class CloudProviderTokens extends Component
public function mount() public function mount()
{ {
$this->authorize('viewAny', CloudProviderToken::class); try {
$this->loadTokens(); $this->authorize('viewAny', CloudProviderToken::class);
$this->loadTokens();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function getListeners() public function getListeners()

View file

@ -21,8 +21,12 @@ public function render()
public function cleanupUnusedKeys() public function cleanupUnusedKeys()
{ {
$this->authorize('create', PrivateKey::class); try {
PrivateKey::cleanupUnusedKeys(); $this->authorize('create', PrivateKey::class);
$this->dispatch('success', 'Unused keys have been cleaned up.'); PrivateKey::cleanupUnusedKeys();
$this->dispatch('success', 'Unused keys have been cleaned up.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
} }

View file

@ -78,14 +78,18 @@ class ByHetzner extends Component
public function mount() public function mount()
{ {
$this->authorize('viewAny', CloudProviderToken::class); try {
$this->loadTokens(); $this->authorize('viewAny', CloudProviderToken::class);
$this->loadSavedCloudInitScripts(); $this->loadTokens();
$this->server_name = generate_random_name(); $this->loadSavedCloudInitScripts();
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get(); $this->server_name = generate_random_name();
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get();
if ($this->private_keys->count() > 0) { if ($this->private_keys->count() > 0) {
$this->private_key_id = $this->private_keys->first()->id; $this->private_key_id = $this->private_keys->first()->id;
}
} catch (\Throwable $e) {
return handleError($e, $this);
} }
} }

View file

@ -96,11 +96,15 @@ public function getConfigurationFilePathProperty(): string
public function changeProxy() public function changeProxy()
{ {
$this->authorize('update', $this->server); try {
$this->server->proxy = null; $this->authorize('update', $this->server);
$this->server->save(); $this->server->proxy = null;
$this->server->save();
$this->dispatch('reloadWindow'); $this->dispatch('reloadWindow');
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function selectProxy($proxy_type) public function selectProxy($proxy_type)

View file

@ -22,34 +22,38 @@ class DynamicConfigurationNavbar extends Component
public function delete(string $fileName) public function delete(string $fileName)
{ {
$this->authorize('update', $this->server); try {
$proxy_path = $this->server->proxyPath(); $this->authorize('update', $this->server);
$proxy_type = $this->server->proxyType(); $proxy_path = $this->server->proxyPath();
$proxy_type = $this->server->proxyType();
// Decode filename: pipes are used to encode dots for Livewire property binding // Decode filename: pipes are used to encode dots for Livewire property binding
// (e.g., 'my|service.yaml' -> 'my.service.yaml') // (e.g., 'my|service.yaml' -> 'my.service.yaml')
// This must happen BEFORE validation because validateShellSafePath() correctly // This must happen BEFORE validation because validateShellSafePath() correctly
// rejects pipe characters as dangerous shell metacharacters // rejects pipe characters as dangerous shell metacharacters
$file = str_replace('|', '.', $fileName); $file = str_replace('|', '.', $fileName);
// Validate filename to prevent command injection // Validate filename to prevent command injection
validateShellSafePath($file, 'proxy configuration filename'); validateShellSafePath($file, 'proxy configuration filename');
if ($proxy_type === 'CADDY' && $file === 'Caddyfile') { if ($proxy_type === 'CADDY' && $file === 'Caddyfile') {
$this->dispatch('error', 'Cannot delete Caddyfile.'); $this->dispatch('error', 'Cannot delete Caddyfile.');
return; return;
}
$fullPath = "{$proxy_path}/dynamic/{$file}";
$escapedPath = escapeshellarg($fullPath);
instant_remote_process(["rm -f {$escapedPath}"], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();
}
$this->dispatch('success', 'File deleted.');
$this->dispatch('loadDynamicConfigurations');
$this->dispatch('refresh');
} catch (\Throwable $e) {
return handleError($e, $this);
} }
$fullPath = "{$proxy_path}/dynamic/{$file}";
$escapedPath = escapeshellarg($fullPath);
instant_remote_process(["rm -f {$escapedPath}"], $this->server);
if ($proxy_type === 'CADDY') {
$this->server->reloadCaddy();
}
$this->dispatch('success', 'File deleted.');
$this->dispatch('loadDynamicConfigurations');
$this->dispatch('refresh');
} }
public function render() public function render()

View file

@ -29,23 +29,38 @@ public function getListeners()
public function startUnmanaged($id) public function startUnmanaged($id)
{ {
$this->server->startUnmanaged($id); try {
$this->dispatch('success', 'Container started.'); $this->authorize('update', $this->server);
$this->loadUnmanagedContainers(); $this->server->startUnmanaged($id);
$this->dispatch('success', 'Container started.');
$this->loadUnmanagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function restartUnmanaged($id) public function restartUnmanaged($id)
{ {
$this->server->restartUnmanaged($id); try {
$this->dispatch('success', 'Container restarted.'); $this->authorize('update', $this->server);
$this->loadUnmanagedContainers(); $this->server->restartUnmanaged($id);
$this->dispatch('success', 'Container restarted.');
$this->loadUnmanagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function stopUnmanaged($id) public function stopUnmanaged($id)
{ {
$this->server->stopUnmanaged($id); try {
$this->dispatch('success', 'Container stopped.'); $this->authorize('update', $this->server);
$this->loadUnmanagedContainers(); $this->server->stopUnmanaged($id);
$this->dispatch('success', 'Container stopped.');
$this->loadUnmanagedContainers();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function refreshStatus() public function refreshStatus()

View file

@ -41,7 +41,11 @@ public function mount()
{ {
$this->parameters = get_route_parameters(); $this->parameters = get_route_parameters();
$this->server = Server::ownedByCurrentTeam()->whereUuid($this->parameters['server_uuid'])->firstOrFail(); $this->server = Server::ownedByCurrentTeam()->whereUuid($this->parameters['server_uuid'])->firstOrFail();
$this->authorize('viewSecurity', $this->server); try {
$this->authorize('viewSecurity', $this->server);
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function checkForUpdatesDispatch() public function checkForUpdatesDispatch()
@ -69,14 +73,14 @@ public function checkForUpdates()
public function updateAllPackages() public function updateAllPackages()
{ {
$this->authorize('update', $this->server);
if (! $this->packageManager || ! $this->osId) {
$this->dispatch('error', message: 'Run "Check for updates" first.');
return;
}
try { try {
$this->authorize('update', $this->server);
if (! $this->packageManager || ! $this->osId) {
$this->dispatch('error', message: 'Run "Check for updates" first.');
return;
}
$activity = UpdatePackage::run( $activity = UpdatePackage::run(
server: $this->server, server: $this->server,
packageManager: $this->packageManager, packageManager: $this->packageManager,
@ -84,8 +88,8 @@ public function updateAllPackages()
all: true all: true
); );
$this->dispatch('activityMonitor', $activity->id, ServerPackageUpdated::class); $this->dispatch('activityMonitor', $activity->id, ServerPackageUpdated::class);
} catch (\Exception $e) { } catch (\Throwable $e) {
$this->dispatch('error', message: $e->getMessage()); return handleError($e, $this);
} }
} }

View file

@ -286,18 +286,22 @@ public function validateServer($install = true)
public function checkLocalhostConnection() public function checkLocalhostConnection()
{ {
$this->syncData(true); try {
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection(); $this->syncData(true);
if ($uptime) { ['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
$this->dispatch('success', 'Server is reachable.'); if ($uptime) {
$this->server->settings->is_reachable = $this->isReachable = true; $this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_usable = $this->isUsable = true; $this->server->settings->is_reachable = $this->isReachable = true;
$this->server->settings->save(); $this->server->settings->is_usable = $this->isUsable = true;
ServerReachabilityChanged::dispatch($this->server); $this->server->settings->save();
} else { ServerReachabilityChanged::dispatch($this->server);
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error); } else {
$this->dispatch('error', 'Server is not reachable.', 'Please validate your configuration and connection.<br><br>Check this <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br>Error: '.$error);
return; return;
}
} catch (\Throwable $e) {
return handleError($e, $this);
} }
} }

View file

@ -72,31 +72,39 @@ public function startValidatingAfterAsking()
public function retry() public function retry()
{ {
$this->authorize('update', $this->server); try {
$this->uptime = null; $this->authorize('update', $this->server);
$this->supported_os_type = null; $this->uptime = null;
$this->prerequisites_installed = null; $this->supported_os_type = null;
$this->docker_installed = null; $this->prerequisites_installed = null;
$this->docker_compose_installed = null; $this->docker_installed = null;
$this->docker_version = null; $this->docker_compose_installed = null;
$this->error = null; $this->docker_version = null;
$this->number_of_tries = 0; $this->error = null;
$this->init(); $this->number_of_tries = 0;
$this->init();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function validateConnection() public function validateConnection()
{ {
$this->authorize('update', $this->server); try {
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection(); $this->authorize('update', $this->server);
if (! $this->uptime) { ['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
$this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>'; if (! $this->uptime) {
$this->server->update([ $this->error = 'Server is not reachable. Please validate your configuration and connection.<br>Check this <a target="_blank" class="text-black underline dark:text-white" href="https://coolify.io/docs/knowledge-base/server/openssh">documentation</a> for further help. <br><br><div class="text-error">Error: '.$error.'</div>';
'validation_logs' => $this->error, $this->server->update([
]); 'validation_logs' => $this->error,
]);
return; return;
}
$this->dispatch('validateOS');
} catch (\Throwable $e) {
return handleError($e, $this);
} }
$this->dispatch('validateOS');
} }
public function validateOS() public function validateOS()

View file

@ -57,9 +57,13 @@ public function mount()
public function switch() public function switch()
{ {
$this->authorize('view', $this->project); try {
$this->view = $this->view === 'normal' ? 'dev' : 'normal'; $this->authorize('view', $this->project);
$this->getDevView(); $this->view = $this->view === 'normal' ? 'dev' : 'normal';
$this->getDevView();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function getDevView() public function getDevView()

View file

@ -51,9 +51,13 @@ public function mount()
public function switch() public function switch()
{ {
$this->authorize('view', $this->team); try {
$this->view = $this->view === 'normal' ? 'dev' : 'normal'; $this->authorize('view', $this->team);
$this->getDevView(); $this->view = $this->view === 'normal' ? 'dev' : 'normal';
$this->getDevView();
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
public function getDevView() public function getDevView()

View file

@ -18,7 +18,11 @@ public function mount()
if (! $this->storage) { if (! $this->storage) {
abort(404); abort(404);
} }
$this->authorize('view', $this->storage); try {
$this->authorize('view', $this->storage);
} catch (\Illuminate\Auth\Access\AuthorizationException) {
return $this->redirectRoute('storage.index', navigate: true);
}
} }
public function render() public function render()

View file

@ -95,23 +95,27 @@ public function submit()
public function delete() public function delete()
{ {
$currentTeam = currentTeam(); try {
$this->authorize('delete', $currentTeam); $currentTeam = currentTeam();
$currentTeam->delete(); $this->authorize('delete', $currentTeam);
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) { $currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === Auth::id()) { if ($user->id === Auth::id()) {
return; return;
} }
$user->teams()->detach($currentTeam); $user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first(); $session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) { if ($session) {
DB::table('sessions')->where('id', $session->id)->delete(); DB::table('sessions')->where('id', $session->id)->delete();
} }
}); });
refreshSession(); refreshSession();
return redirect()->route('team.index'); return redirect()->route('team.index');
} catch (\Throwable $e) {
return handleError($e, $this);
}
} }
} }

View file

@ -59,7 +59,7 @@ protected static function booted()
$team->webhookNotificationSettings()->create(); $team->webhookNotificationSettings()->create();
}); });
static::saving(function ($team) { static::updating(function ($team) {
if (auth()->user()?->isMember()) { if (auth()->user()?->isMember()) {
throw new \Exception('You are not allowed to update this team.'); throw new \Exception('You are not allowed to update this team.');
} }

View file

@ -12,11 +12,6 @@ class ApiTokenPolicy
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
// Authorization temporarily disabled
/*
// Users can view their own API tokens
return true;
*/
return true; return true;
} }
@ -25,12 +20,7 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, PersonalAccessToken $token): bool public function view(User $user, PersonalAccessToken $token): bool
{ {
// Authorization temporarily disabled
/*
// Users can only view their own tokens
return $user->id === $token->tokenable_id && $token->tokenable_type === User::class; return $user->id === $token->tokenable_id && $token->tokenable_type === User::class;
*/
return true;
} }
/** /**
@ -38,11 +28,6 @@ public function view(User $user, PersonalAccessToken $token): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// Authorization temporarily disabled
/*
// All authenticated users can create their own API tokens
return true;
*/
return true; return true;
} }
@ -51,12 +36,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, PersonalAccessToken $token): bool public function update(User $user, PersonalAccessToken $token): bool
{ {
// Authorization temporarily disabled
/*
// Users can only update their own tokens
return $user->id === $token->tokenable_id && $token->tokenable_type === User::class; return $user->id === $token->tokenable_id && $token->tokenable_type === User::class;
*/
return true;
} }
/** /**
@ -64,12 +44,7 @@ public function update(User $user, PersonalAccessToken $token): bool
*/ */
public function delete(User $user, PersonalAccessToken $token): bool public function delete(User $user, PersonalAccessToken $token): bool
{ {
// Authorization temporarily disabled
/*
// Users can only delete their own tokens
return $user->id === $token->tokenable_id && $token->tokenable_type === User::class; return $user->id === $token->tokenable_id && $token->tokenable_type === User::class;
*/
return true;
} }
/** /**
@ -77,11 +52,6 @@ public function delete(User $user, PersonalAccessToken $token): bool
*/ */
public function manage(User $user): bool public function manage(User $user): bool
{ {
// Authorization temporarily disabled
/*
// All authenticated users can manage their own API tokens
return true;
*/
return true; return true;
} }
@ -90,7 +60,6 @@ public function manage(User $user): bool
*/ */
public function useRootPermissions(User $user): bool public function useRootPermissions(User $user): bool
{ {
// Only admins and owners can use root permissions
return $user->isAdmin() || $user->isOwner(); return $user->isAdmin() || $user->isOwner();
} }
@ -99,11 +68,14 @@ public function useRootPermissions(User $user): bool
*/ */
public function useWritePermissions(User $user): bool public function useWritePermissions(User $user): bool
{ {
// Authorization temporarily disabled
/*
// Only admins and owners can use write permissions
return $user->isAdmin() || $user->isOwner(); return $user->isAdmin() || $user->isOwner();
*/ }
return true;
/**
* Determine whether the user can use deploy permissions for API tokens.
*/
public function useDeployPermissions(User $user): bool
{
return $user->isAdmin() || $user->isOwner();
} }
} }

View file

@ -13,10 +13,6 @@ class ApplicationPolicy
*/ */
public function viewAny(User $user): bool public function viewAny(User $user): bool
{ {
// Authorization temporarily disabled
/*
return true;
*/
return true; return true;
} }
@ -25,11 +21,9 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, Application $application): bool public function view(User $user, Application $application): bool
{ {
// Authorization temporarily disabled $teamId = $this->getTeamId($application);
/*
return true; return $teamId !== null && $user->teams->contains('id', $teamId);
*/
return true;
} }
/** /**
@ -37,15 +31,7 @@ public function view(User $user, Application $application): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// Authorization temporarily disabled return $user->isAdmin();
/*
if ($user->isAdmin()) {
return true;
}
return false;
*/
return true;
} }
/** /**
@ -53,15 +39,17 @@ public function create(User $user): bool
*/ */
public function update(User $user, Application $application): Response public function update(User $user, Application $application): Response
{ {
// Authorization temporarily disabled $teamId = $this->getTeamId($application);
/*
if ($user->isAdmin()) { if ($teamId === null) {
return Response::deny('Application team not found.');
}
if ($user->isAdminOfTeam($teamId)) {
return Response::allow(); return Response::allow();
} }
return Response::deny('As a member, you cannot update this application.<br/><br/>You need at least admin or owner permissions.'); return Response::deny('You need at least admin or owner permissions to update this application.');
*/
return Response::allow();
} }
/** /**
@ -69,15 +57,9 @@ public function update(User $user, Application $application): Response
*/ */
public function delete(User $user, Application $application): bool public function delete(User $user, Application $application): bool
{ {
// Authorization temporarily disabled $teamId = $this->getTeamId($application);
/*
if ($user->isAdmin()) {
return true;
}
return false; return $teamId !== null && $user->isAdminOfTeam($teamId);
*/
return true;
} }
/** /**
@ -85,11 +67,7 @@ public function delete(User $user, Application $application): bool
*/ */
public function restore(User $user, Application $application): bool public function restore(User $user, Application $application): bool
{ {
// Authorization temporarily disabled return false;
/*
return true;
*/
return true;
} }
/** /**
@ -97,11 +75,7 @@ public function restore(User $user, Application $application): bool
*/ */
public function forceDelete(User $user, Application $application): bool public function forceDelete(User $user, Application $application): bool
{ {
// Authorization temporarily disabled return false;
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
} }
/** /**
@ -109,11 +83,9 @@ public function forceDelete(User $user, Application $application): bool
*/ */
public function deploy(User $user, Application $application): bool public function deploy(User $user, Application $application): bool
{ {
// Authorization temporarily disabled $teamId = $this->getTeamId($application);
/*
return $user->teams->contains('id', $application->team()->first()->id); return $teamId !== null && $user->isAdminOfTeam($teamId);
*/
return true;
} }
/** /**
@ -121,11 +93,9 @@ public function deploy(User $user, Application $application): bool
*/ */
public function manageDeployments(User $user, Application $application): bool public function manageDeployments(User $user, Application $application): bool
{ {
// Authorization temporarily disabled $teamId = $this->getTeamId($application);
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id); return $teamId !== null && $user->isAdminOfTeam($teamId);
*/
return true;
} }
/** /**
@ -133,11 +103,9 @@ public function manageDeployments(User $user, Application $application): bool
*/ */
public function manageEnvironment(User $user, Application $application): bool public function manageEnvironment(User $user, Application $application): bool
{ {
// Authorization temporarily disabled $teamId = $this->getTeamId($application);
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id); return $teamId !== null && $user->isAdminOfTeam($teamId);
*/
return true;
} }
/** /**
@ -145,10 +113,11 @@ public function manageEnvironment(User $user, Application $application): bool
*/ */
public function cleanupDeploymentQueue(User $user): bool public function cleanupDeploymentQueue(User $user): bool
{ {
// Authorization temporarily disabled
/*
return $user->isAdmin(); return $user->isAdmin();
*/ }
return true;
private function getTeamId(Application $application): ?int
{
return $application->team()?->id;
} }
} }

View file

@ -21,8 +21,9 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, ApplicationPreview $applicationPreview): bool public function view(User $user, ApplicationPreview $applicationPreview): bool
{ {
// return $user->teams->contains('id', $applicationPreview->application->team()->first()->id); $teamId = $this->getTeamId($applicationPreview);
return true;
return $teamId !== null && $user->teams->contains('id', $teamId);
} }
/** /**
@ -30,21 +31,25 @@ public function view(User $user, ApplicationPreview $applicationPreview): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
* Determine whether the user can update the model. * Determine whether the user can update the model.
*/ */
public function update(User $user, ApplicationPreview $applicationPreview) public function update(User $user, ApplicationPreview $applicationPreview): Response
{ {
// if ($user->isAdmin()) { $teamId = $this->getTeamId($applicationPreview);
// return Response::allow();
// }
// return Response::deny('As a member, you cannot update this preview.<br/><br/>You need at least admin or owner permissions.'); if ($teamId === null) {
return true; return Response::deny('Application preview team not found.');
}
if ($user->isAdminOfTeam($teamId)) {
return Response::allow();
}
return Response::deny('You need at least admin or owner permissions to update this preview.');
} }
/** /**
@ -52,8 +57,9 @@ public function update(User $user, ApplicationPreview $applicationPreview)
*/ */
public function delete(User $user, ApplicationPreview $applicationPreview): bool public function delete(User $user, ApplicationPreview $applicationPreview): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id); $teamId = $this->getTeamId($applicationPreview);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -61,8 +67,7 @@ public function delete(User $user, ApplicationPreview $applicationPreview): bool
*/ */
public function restore(User $user, ApplicationPreview $applicationPreview): bool public function restore(User $user, ApplicationPreview $applicationPreview): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id); return false;
return true;
} }
/** /**
@ -70,8 +75,7 @@ public function restore(User $user, ApplicationPreview $applicationPreview): boo
*/ */
public function forceDelete(User $user, ApplicationPreview $applicationPreview): bool public function forceDelete(User $user, ApplicationPreview $applicationPreview): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id); return false;
return true;
} }
/** /**
@ -79,8 +83,9 @@ public function forceDelete(User $user, ApplicationPreview $applicationPreview):
*/ */
public function deploy(User $user, ApplicationPreview $applicationPreview): bool public function deploy(User $user, ApplicationPreview $applicationPreview): bool
{ {
// return $user->teams->contains('id', $applicationPreview->application->team()->first()->id); $teamId = $this->getTeamId($applicationPreview);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -88,7 +93,13 @@ public function deploy(User $user, ApplicationPreview $applicationPreview): bool
*/ */
public function manageDeployments(User $user, ApplicationPreview $applicationPreview): bool public function manageDeployments(User $user, ApplicationPreview $applicationPreview): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationPreview->application->team()->first()->id); $teamId = $this->getTeamId($applicationPreview);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
private function getTeamId(ApplicationPreview $applicationPreview): ?int
{
return $applicationPreview->application?->team()?->id;
} }
} }

View file

@ -20,8 +20,9 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, ApplicationSetting $applicationSetting): bool public function view(User $user, ApplicationSetting $applicationSetting): bool
{ {
// return $user->teams->contains('id', $applicationSetting->application->team()->first()->id); $teamId = $this->getTeamId($applicationSetting);
return true;
return $teamId !== null && $user->teams->contains('id', $teamId);
} }
/** /**
@ -29,8 +30,7 @@ public function view(User $user, ApplicationSetting $applicationSetting): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -38,8 +38,9 @@ public function create(User $user): bool
*/ */
public function update(User $user, ApplicationSetting $applicationSetting): bool public function update(User $user, ApplicationSetting $applicationSetting): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id); $teamId = $this->getTeamId($applicationSetting);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -47,8 +48,9 @@ public function update(User $user, ApplicationSetting $applicationSetting): bool
*/ */
public function delete(User $user, ApplicationSetting $applicationSetting): bool public function delete(User $user, ApplicationSetting $applicationSetting): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id); $teamId = $this->getTeamId($applicationSetting);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -56,8 +58,7 @@ public function delete(User $user, ApplicationSetting $applicationSetting): bool
*/ */
public function restore(User $user, ApplicationSetting $applicationSetting): bool public function restore(User $user, ApplicationSetting $applicationSetting): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id); return false;
return true;
} }
/** /**
@ -65,7 +66,11 @@ public function restore(User $user, ApplicationSetting $applicationSetting): boo
*/ */
public function forceDelete(User $user, ApplicationSetting $applicationSetting): bool public function forceDelete(User $user, ApplicationSetting $applicationSetting): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $applicationSetting->application->team()->first()->id); return false;
return true; }
private function getTeamId(ApplicationSetting $applicationSetting): ?int
{
return $applicationSetting->application?->team()?->id;
} }
} }

View file

@ -20,8 +20,9 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, $database): bool public function view(User $user, $database): bool
{ {
// return $user->teams->contains('id', $database->team()->first()->id); $teamId = $this->getTeamId($database);
return true;
return $teamId !== null && $user->teams->contains('id', $teamId);
} }
/** /**
@ -29,21 +30,25 @@ public function view(User $user, $database): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
* Determine whether the user can update the model. * Determine whether the user can update the model.
*/ */
public function update(User $user, $database) public function update(User $user, $database): Response
{ {
// if ($user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id)) { $teamId = $this->getTeamId($database);
// return Response::allow();
// }
// return Response::deny('As a member, you cannot update this database.<br/><br/>You need at least admin or owner permissions.'); if ($teamId === null) {
return true; return Response::deny('Database team not found.');
}
if ($user->isAdminOfTeam($teamId)) {
return Response::allow();
}
return Response::deny('You need at least admin or owner permissions to update this database.');
} }
/** /**
@ -51,8 +56,9 @@ public function update(User $user, $database)
*/ */
public function delete(User $user, $database): bool public function delete(User $user, $database): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id); $teamId = $this->getTeamId($database);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -60,8 +66,7 @@ public function delete(User $user, $database): bool
*/ */
public function restore(User $user, $database): bool public function restore(User $user, $database): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id); return false;
return true;
} }
/** /**
@ -69,8 +74,7 @@ public function restore(User $user, $database): bool
*/ */
public function forceDelete(User $user, $database): bool public function forceDelete(User $user, $database): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id); return false;
return true;
} }
/** /**
@ -78,8 +82,9 @@ public function forceDelete(User $user, $database): bool
*/ */
public function manage(User $user, $database): bool public function manage(User $user, $database): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id); $teamId = $this->getTeamId($database);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -87,8 +92,9 @@ public function manage(User $user, $database): bool
*/ */
public function manageBackups(User $user, $database): bool public function manageBackups(User $user, $database): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id); $teamId = $this->getTeamId($database);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -96,7 +102,17 @@ public function manageBackups(User $user, $database): bool
*/ */
public function manageEnvironment(User $user, $database): bool public function manageEnvironment(User $user, $database): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id); $teamId = $this->getTeamId($database);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
private function getTeamId($database): ?int
{
if (method_exists($database, 'team')) {
return $database->team()?->id;
}
return null;
} }
} }

View file

@ -20,8 +20,9 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, Environment $environment): bool public function view(User $user, Environment $environment): bool
{ {
// return $user->teams->contains('id', $environment->project->team_id); $teamId = $this->getTeamId($environment);
return true;
return $teamId !== null && $user->teams->contains('id', $teamId);
} }
/** /**
@ -29,8 +30,7 @@ public function view(User $user, Environment $environment): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -38,8 +38,9 @@ public function create(User $user): bool
*/ */
public function update(User $user, Environment $environment): bool public function update(User $user, Environment $environment): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id); $teamId = $this->getTeamId($environment);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -47,8 +48,9 @@ public function update(User $user, Environment $environment): bool
*/ */
public function delete(User $user, Environment $environment): bool public function delete(User $user, Environment $environment): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id); $teamId = $this->getTeamId($environment);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -56,8 +58,7 @@ public function delete(User $user, Environment $environment): bool
*/ */
public function restore(User $user, Environment $environment): bool public function restore(User $user, Environment $environment): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id); return false;
return true;
} }
/** /**
@ -65,7 +66,11 @@ public function restore(User $user, Environment $environment): bool
*/ */
public function forceDelete(User $user, Environment $environment): bool public function forceDelete(User $user, Environment $environment): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id); return false;
return true; }
private function getTeamId(Environment $environment): ?int
{
return $environment->project?->team_id;
} }
} }

View file

@ -20,7 +20,9 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, EnvironmentVariable $environmentVariable): bool public function view(User $user, EnvironmentVariable $environmentVariable): bool
{ {
return true; $teamId = $this->getTeamId($environmentVariable);
return $teamId !== null && $user->teams->contains('id', $teamId);
} }
/** /**
@ -28,7 +30,7 @@ public function view(User $user, EnvironmentVariable $environmentVariable): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
return true; return $user->isAdmin();
} }
/** /**
@ -36,7 +38,9 @@ public function create(User $user): bool
*/ */
public function update(User $user, EnvironmentVariable $environmentVariable): bool public function update(User $user, EnvironmentVariable $environmentVariable): bool
{ {
return true; $teamId = $this->getTeamId($environmentVariable);
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -44,7 +48,9 @@ public function update(User $user, EnvironmentVariable $environmentVariable): bo
*/ */
public function delete(User $user, EnvironmentVariable $environmentVariable): bool public function delete(User $user, EnvironmentVariable $environmentVariable): bool
{ {
return true; $teamId = $this->getTeamId($environmentVariable);
return $teamId !== null && $user->isAdminOfTeam($teamId);
} }
/** /**
@ -52,7 +58,7 @@ public function delete(User $user, EnvironmentVariable $environmentVariable): bo
*/ */
public function restore(User $user, EnvironmentVariable $environmentVariable): bool public function restore(User $user, EnvironmentVariable $environmentVariable): bool
{ {
return true; return false;
} }
/** /**
@ -60,7 +66,7 @@ public function restore(User $user, EnvironmentVariable $environmentVariable): b
*/ */
public function forceDelete(User $user, EnvironmentVariable $environmentVariable): bool public function forceDelete(User $user, EnvironmentVariable $environmentVariable): bool
{ {
return true; return false;
} }
/** /**
@ -68,6 +74,19 @@ public function forceDelete(User $user, EnvironmentVariable $environmentVariable
*/ */
public function manageEnvironment(User $user, EnvironmentVariable $environmentVariable): bool public function manageEnvironment(User $user, EnvironmentVariable $environmentVariable): bool
{ {
return true; $teamId = $this->getTeamId($environmentVariable);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
private function getTeamId(EnvironmentVariable $environmentVariable): ?int
{
$resource = $environmentVariable->resourceable;
if (! $resource || ! method_exists($resource, 'team')) {
return null;
}
return $resource->team()?->id;
} }
} }

View file

@ -20,8 +20,11 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, GithubApp $githubApp): bool public function view(User $user, GithubApp $githubApp): bool
{ {
// return $user->teams->contains('id', $githubApp->team_id) || $githubApp->is_system_wide; if ($githubApp->is_system_wide) {
return true; return true;
}
return $user->teams->contains('id', $githubApp->team_id);
} }
/** /**
@ -29,8 +32,7 @@ public function view(User $user, GithubApp $githubApp): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -39,12 +41,10 @@ public function create(User $user): bool
public function update(User $user, GithubApp $githubApp): bool public function update(User $user, GithubApp $githubApp): bool
{ {
if ($githubApp->is_system_wide) { if ($githubApp->is_system_wide) {
// return $user->isAdmin(); return $user->canAccessSystemResources();
return true;
} }
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id); return $user->isAdminOfTeam($githubApp->team_id);
return true;
} }
/** /**
@ -53,12 +53,10 @@ public function update(User $user, GithubApp $githubApp): bool
public function delete(User $user, GithubApp $githubApp): bool public function delete(User $user, GithubApp $githubApp): bool
{ {
if ($githubApp->is_system_wide) { if ($githubApp->is_system_wide) {
// return $user->isAdmin(); return $user->canAccessSystemResources();
return true;
} }
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id); return $user->isAdminOfTeam($githubApp->team_id);
return true;
} }
/** /**

View file

@ -12,13 +12,11 @@ class NotificationPolicy
*/ */
public function view(User $user, Model $notificationSettings): bool public function view(User $user, Model $notificationSettings): bool
{ {
// Check if the notification settings belong to the user's current team
if (! $notificationSettings->team) { if (! $notificationSettings->team) {
return false; return false;
} }
// return $user->teams()->where('teams.id', $notificationSettings->team->id)->exists(); return $user->teams->contains('id', $notificationSettings->team->id);
return true;
} }
/** /**
@ -26,14 +24,13 @@ public function view(User $user, Model $notificationSettings): bool
*/ */
public function update(User $user, Model $notificationSettings): bool public function update(User $user, Model $notificationSettings): bool
{ {
// Check if the notification settings belong to the user's current team
if (! $notificationSettings->team) { if (! $notificationSettings->team) {
return false; return false;
} }
// Only owners and admins can update notification settings $teamId = $notificationSettings->team->id;
// return $user->isAdmin() || $user->isOwner();
return true; return $user->isAdminOfTeam($teamId);
} }
/** /**
@ -41,8 +38,7 @@ public function update(User $user, Model $notificationSettings): bool
*/ */
public function manage(User $user, Model $notificationSettings): bool public function manage(User $user, Model $notificationSettings): bool
{ {
// return $this->update($user, $notificationSettings); return $this->update($user, $notificationSettings);
return true;
} }
/** /**
@ -50,7 +46,6 @@ public function manage(User $user, Model $notificationSettings): bool
*/ */
public function sendTest(User $user, Model $notificationSettings): bool public function sendTest(User $user, Model $notificationSettings): bool
{ {
// return $this->update($user, $notificationSettings); return $this->update($user, $notificationSettings);
return true;
} }
} }

View file

@ -20,8 +20,7 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, Project $project): bool public function view(User $user, Project $project): bool
{ {
// return $user->teams->contains('id', $project->team_id); return $user->teams->contains('id', $project->team_id);
return true;
} }
/** /**
@ -29,8 +28,7 @@ public function view(User $user, Project $project): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -38,8 +36,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, Project $project): bool public function update(User $user, Project $project): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id); return $user->isAdminOfTeam($project->team_id);
return true;
} }
/** /**
@ -47,8 +44,7 @@ public function update(User $user, Project $project): bool
*/ */
public function delete(User $user, Project $project): bool public function delete(User $user, Project $project): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id); return $user->isAdminOfTeam($project->team_id);
return true;
} }
/** /**
@ -56,8 +52,7 @@ public function delete(User $user, Project $project): bool
*/ */
public function restore(User $user, Project $project): bool public function restore(User $user, Project $project): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id); return false;
return true;
} }
/** /**
@ -65,7 +60,6 @@ public function restore(User $user, Project $project): bool
*/ */
public function forceDelete(User $user, Project $project): bool public function forceDelete(User $user, Project $project): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $project->team_id); return false;
return true;
} }
} }

View file

@ -38,8 +38,7 @@ class ResourceCreatePolicy
*/ */
public function createAny(User $user): bool public function createAny(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -51,8 +50,7 @@ public function create(User $user, string $resourceClass): bool
return false; return false;
} }
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**

View file

@ -28,8 +28,7 @@ public function view(User $user, Server $server): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -37,8 +36,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, Server $server): bool public function update(User $user, Server $server): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id); return $user->isAdminOfTeam($server->team_id);
return true;
} }
/** /**
@ -46,8 +44,7 @@ public function update(User $user, Server $server): bool
*/ */
public function delete(User $user, Server $server): bool public function delete(User $user, Server $server): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id); return $user->isAdminOfTeam($server->team_id);
return true;
} }
/** /**
@ -71,8 +68,7 @@ public function forceDelete(User $user, Server $server): bool
*/ */
public function manageProxy(User $user, Server $server): bool public function manageProxy(User $user, Server $server): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id); return $user->isAdminOfTeam($server->team_id);
return true;
} }
/** /**
@ -80,8 +76,7 @@ public function manageProxy(User $user, Server $server): bool
*/ */
public function manageSentinel(User $user, Server $server): bool public function manageSentinel(User $user, Server $server): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id); return $user->isAdminOfTeam($server->team_id);
return true;
} }
/** /**
@ -89,8 +84,7 @@ public function manageSentinel(User $user, Server $server): bool
*/ */
public function manageCaCertificate(User $user, Server $server): bool public function manageCaCertificate(User $user, Server $server): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id); return $user->isAdminOfTeam($server->team_id);
return true;
} }
/** /**
@ -98,7 +92,6 @@ public function manageCaCertificate(User $user, Server $server): bool
*/ */
public function viewSecurity(User $user, Server $server): bool public function viewSecurity(User $user, Server $server): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $server->team_id); return $user->isAdminOfTeam($server->team_id);
return true;
} }
} }

View file

@ -21,8 +21,7 @@ public function view(User $user, ServiceApplication $serviceApplication): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -30,8 +29,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, ServiceApplication $serviceApplication): bool public function update(User $user, ServiceApplication $serviceApplication): bool
{ {
// return Gate::allows('update', $serviceApplication->service); return Gate::allows('update', $serviceApplication->service);
return true;
} }
/** /**
@ -39,8 +37,7 @@ public function update(User $user, ServiceApplication $serviceApplication): bool
*/ */
public function delete(User $user, ServiceApplication $serviceApplication): bool public function delete(User $user, ServiceApplication $serviceApplication): bool
{ {
// return Gate::allows('delete', $serviceApplication->service); return Gate::allows('delete', $serviceApplication->service);
return true;
} }
/** /**
@ -48,8 +45,7 @@ public function delete(User $user, ServiceApplication $serviceApplication): bool
*/ */
public function restore(User $user, ServiceApplication $serviceApplication): bool public function restore(User $user, ServiceApplication $serviceApplication): bool
{ {
// return Gate::allows('update', $serviceApplication->service); return false;
return true;
} }
/** /**
@ -57,7 +53,6 @@ public function restore(User $user, ServiceApplication $serviceApplication): boo
*/ */
public function forceDelete(User $user, ServiceApplication $serviceApplication): bool public function forceDelete(User $user, ServiceApplication $serviceApplication): bool
{ {
// return Gate::allows('delete', $serviceApplication->service); return false;
return true;
} }
} }

View file

@ -13,7 +13,7 @@ class ServiceDatabasePolicy
*/ */
public function view(User $user, ServiceDatabase $serviceDatabase): bool public function view(User $user, ServiceDatabase $serviceDatabase): bool
{ {
return true; return Gate::allows('view', $serviceDatabase->service);
} }
/** /**
@ -21,8 +21,7 @@ public function view(User $user, ServiceDatabase $serviceDatabase): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -30,9 +29,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, ServiceDatabase $serviceDatabase): bool public function update(User $user, ServiceDatabase $serviceDatabase): bool
{ {
return Gate::allows('update', $serviceDatabase->service);
// return Gate::allows('update', $serviceDatabase->service);
return true;
} }
/** /**
@ -40,8 +37,7 @@ public function update(User $user, ServiceDatabase $serviceDatabase): bool
*/ */
public function delete(User $user, ServiceDatabase $serviceDatabase): bool public function delete(User $user, ServiceDatabase $serviceDatabase): bool
{ {
// return Gate::allows('delete', $serviceDatabase->service); return Gate::allows('delete', $serviceDatabase->service);
return true;
} }
/** /**
@ -49,8 +45,7 @@ public function delete(User $user, ServiceDatabase $serviceDatabase): bool
*/ */
public function restore(User $user, ServiceDatabase $serviceDatabase): bool public function restore(User $user, ServiceDatabase $serviceDatabase): bool
{ {
// return Gate::allows('update', $serviceDatabase->service); return false;
return true;
} }
/** /**
@ -58,12 +53,14 @@ public function restore(User $user, ServiceDatabase $serviceDatabase): bool
*/ */
public function forceDelete(User $user, ServiceDatabase $serviceDatabase): bool public function forceDelete(User $user, ServiceDatabase $serviceDatabase): bool
{ {
// return Gate::allows('delete', $serviceDatabase->service); return false;
return true;
} }
/**
* Determine whether the user can manage database backups.
*/
public function manageBackups(User $user, ServiceDatabase $serviceDatabase): bool public function manageBackups(User $user, ServiceDatabase $serviceDatabase): bool
{ {
return true; return Gate::allows('update', $serviceDatabase->service);
} }
} }

View file

@ -20,7 +20,9 @@ public function viewAny(User $user): bool
*/ */
public function view(User $user, Service $service): bool public function view(User $user, Service $service): bool
{ {
return true; $teamId = $this->getTeamId($service);
return $teamId !== null && $user->teams->contains('id', $teamId);
} }
/** /**
@ -28,8 +30,7 @@ public function view(User $user, Service $service): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -37,13 +38,9 @@ public function create(User $user): bool
*/ */
public function update(User $user, Service $service): bool public function update(User $user, Service $service): bool
{ {
$team = $service->team(); $teamId = $this->getTeamId($service);
if (! $team) {
return false;
}
// return $user->isAdmin() && $user->teams->contains('id', $team->id); return $teamId !== null && $user->isAdminOfTeam($teamId);
return true;
} }
/** /**
@ -51,12 +48,9 @@ public function update(User $user, Service $service): bool
*/ */
public function delete(User $user, Service $service): bool public function delete(User $user, Service $service): bool
{ {
// if ($user->isAdmin()) { $teamId = $this->getTeamId($service);
// return true;
// }
// return false; return $teamId !== null && $user->isAdminOfTeam($teamId);
return true;
} }
/** /**
@ -64,8 +58,7 @@ public function delete(User $user, Service $service): bool
*/ */
public function restore(User $user, Service $service): bool public function restore(User $user, Service $service): bool
{ {
// return true; return false;
return true;
} }
/** /**
@ -73,23 +66,17 @@ public function restore(User $user, Service $service): bool
*/ */
public function forceDelete(User $user, Service $service): bool public function forceDelete(User $user, Service $service): bool
{ {
// if ($user->isAdmin()) { return false;
// return true;
// }
// return false;
return true;
} }
/**
* Determine whether the user can stop the service.
*/
public function stop(User $user, Service $service): bool public function stop(User $user, Service $service): bool
{ {
$team = $service->team(); $teamId = $this->getTeamId($service);
if (! $team) {
return false;
}
// return $user->teams->contains('id', $team->id); return $teamId !== null && $user->isAdminOfTeam($teamId);
return true;
} }
/** /**
@ -97,13 +84,9 @@ public function stop(User $user, Service $service): bool
*/ */
public function manageEnvironment(User $user, Service $service): bool public function manageEnvironment(User $user, Service $service): bool
{ {
$team = $service->team(); $teamId = $this->getTeamId($service);
if (! $team) {
return false;
}
// return $user->isAdmin() && $user->teams->contains('id', $team->id); return $teamId !== null && $user->isAdminOfTeam($teamId);
return true;
} }
/** /**
@ -111,18 +94,23 @@ public function manageEnvironment(User $user, Service $service): bool
*/ */
public function deploy(User $user, Service $service): bool public function deploy(User $user, Service $service): bool
{ {
$team = $service->team(); $teamId = $this->getTeamId($service);
if (! $team) {
return false;
}
// return $user->teams->contains('id', $team->id); return $teamId !== null && $user->isAdminOfTeam($teamId);
return true;
} }
/**
* Determine whether the user can access the terminal.
*/
public function accessTerminal(User $user, Service $service): bool public function accessTerminal(User $user, Service $service): bool
{ {
// return $user->isAdmin() || $user->teams->contains('id', $service->team()->id); $teamId = $this->getTeamId($service);
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
private function getTeamId(Service $service): ?int
{
return $service->team()?->id;
} }
} }

View file

@ -28,8 +28,7 @@ public function view(User $user, SharedEnvironmentVariable $sharedEnvironmentVar
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -37,8 +36,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool public function update(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id); return $user->isAdminOfTeam($sharedEnvironmentVariable->team_id);
return true;
} }
/** /**
@ -46,8 +44,7 @@ public function update(User $user, SharedEnvironmentVariable $sharedEnvironmentV
*/ */
public function delete(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool public function delete(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id); return $user->isAdminOfTeam($sharedEnvironmentVariable->team_id);
return true;
} }
/** /**
@ -55,8 +52,7 @@ public function delete(User $user, SharedEnvironmentVariable $sharedEnvironmentV
*/ */
public function restore(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool public function restore(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id); return false;
return true;
} }
/** /**
@ -64,8 +60,7 @@ public function restore(User $user, SharedEnvironmentVariable $sharedEnvironment
*/ */
public function forceDelete(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool public function forceDelete(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id); return false;
return true;
} }
/** /**
@ -73,7 +68,6 @@ public function forceDelete(User $user, SharedEnvironmentVariable $sharedEnviron
*/ */
public function manageEnvironment(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool public function manageEnvironment(User $user, SharedEnvironmentVariable $sharedEnvironmentVariable): bool
{ {
// return $user->isAdmin() && $user->teams->contains('id', $sharedEnvironmentVariable->team_id); return $user->isAdminOfTeam($sharedEnvironmentVariable->team_id);
return true;
} }
} }

View file

@ -28,8 +28,7 @@ public function view(User $user, StandaloneDocker $standaloneDocker): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -37,7 +36,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, StandaloneDocker $standaloneDocker): bool public function update(User $user, StandaloneDocker $standaloneDocker): bool
{ {
return $user->teams->contains('id', $standaloneDocker->server->team_id); return $user->isAdminOfTeam($standaloneDocker->server->team_id);
} }
/** /**
@ -45,7 +44,7 @@ public function update(User $user, StandaloneDocker $standaloneDocker): bool
*/ */
public function delete(User $user, StandaloneDocker $standaloneDocker): bool public function delete(User $user, StandaloneDocker $standaloneDocker): bool
{ {
return $user->teams->contains('id', $standaloneDocker->server->team_id); return $user->isAdminOfTeam($standaloneDocker->server->team_id);
} }
/** /**

View file

@ -28,8 +28,7 @@ public function view(User $user, SwarmDocker $swarmDocker): bool
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -37,7 +36,7 @@ public function create(User $user): bool
*/ */
public function update(User $user, SwarmDocker $swarmDocker): bool public function update(User $user, SwarmDocker $swarmDocker): bool
{ {
return $user->teams->contains('id', $swarmDocker->server->team_id); return $user->isAdminOfTeam($swarmDocker->server->team_id);
} }
/** /**
@ -45,7 +44,7 @@ public function update(User $user, SwarmDocker $swarmDocker): bool
*/ */
public function delete(User $user, SwarmDocker $swarmDocker): bool public function delete(User $user, SwarmDocker $swarmDocker): bool
{ {
return $user->teams->contains('id', $swarmDocker->server->team_id); return $user->isAdminOfTeam($swarmDocker->server->team_id);
} }
/** /**

View file

@ -3,7 +3,7 @@
Advanced Advanced
</x-slot> </x-slot>
@if ($application->status === 'running') @if ($application->status === 'running')
<div class="dropdown-iteme" wire:click='force_deploy_without_cache'> <div class="dropdown-iteme" @if(!auth()->user()->can('deploy', $application)) data-disabled @endif wire:click='force_deploy_without_cache'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -18,7 +18,7 @@
cache) cache)
</div> </div>
@else @else
<div class="dropdown-item" wire:click='deploy(true)'> <div class="dropdown-item" @if(!auth()->user()->can('deploy', $application)) data-disabled @endif wire:click='deploy(true)'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" stroke-width="1.5" <svg xmlns="http://www.w3.org/2000/svg" class="w-4 h-4" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />

View file

@ -2,7 +2,7 @@
<h1>Notifications</h1> <h1>Notifications</h1>
<div class="subtitle">Get notified about your infrastructure.</div> <div class="subtitle">Get notified about your infrastructure.</div>
<div class="navbar-main"> <div class="navbar-main">
<nav class="flex items-center gap-3.5 min-h-10"> <nav class="flex items-center gap-6 min-h-10">
<a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}" {{ wireNavigate() }} <a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}" {{ wireNavigate() }}
href="{{ route('notifications.email') }}"> href="{{ route('notifications.email') }}">
<button>Email</button> <button>Email</button>

View file

@ -3,7 +3,7 @@
Advanced Advanced
</x-slot> </x-slot>
@if (str($service->status)->contains('running')) @if (str($service->status)->contains('running'))
<div class="dropdown-item" @click="$wire.dispatch('pullAndRestartEvent')"> <div class="dropdown-item" @if(!auth()->user()->can('deploy', $service)) data-disabled @endif @click="$wire.dispatch('pullAndRestartEvent')">
<svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5" <svg xmlns="http://www.w3.org/2000/svg" class="w-6 h-6" viewBox="0 0 24 24" stroke-width="1.5"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -17,7 +17,7 @@
Pull Latest Images & Restart Pull Latest Images & Restart
</div> </div>
@elseif (str($service->status)->contains('degraded')) @elseif (str($service->status)->contains('degraded'))
<div class="dropdown-item" @click="$wire.dispatch('forceDeployEvent')"> <div class="dropdown-item" @if(!auth()->user()->can('deploy', $service)) data-disabled @endif @click="$wire.dispatch('forceDeployEvent')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke: currentColor;" class="w-6 h-6" stroke-width="2"> style="--darkreader-inline-stroke: currentColor;" class="w-6 h-6" stroke-width="2">
@ -27,7 +27,7 @@
Force Restart Force Restart
</div> </div>
@else @else
<div class="dropdown-item" @click="$wire.dispatch('forceDeployEvent')"> <div class="dropdown-item" @if(!auth()->user()->can('deploy', $service)) data-disabled @endif @click="$wire.dispatch('forceDeployEvent')">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke="" stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke: currentColor;" class="w-4 h-4" stroke-width="2"> style="--darkreader-inline-stroke: currentColor;" class="w-4 h-4" stroke-width="2">
@ -36,7 +36,7 @@
</svg> </svg>
Force Deploy Force Deploy
</div> </div>
<div class="dropdown-item" wire:click='stop(true)''> <div class="dropdown-item" @if(!auth()->user()->can('stop', $service)) data-disabled @endif wire:click='stop(true)''>
<svg class="w-4 h-4" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"> <svg class="w-4 h-4" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" /> <path fill="currentColor" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
<path fill="currentColor" <path fill="currentColor"

View file

@ -36,88 +36,88 @@
<div>Please load a Compose file.</div> <div>Please load a Compose file.</div>
@else @else
@if (!$application->destination->server->isSwarm()) @if (!$application->destination->server->isSwarm())
<div> <div>
<x-applications.advanced :application="$application" /> <x-applications.advanced :application="$application" />
</div> </div>
@endif @endif
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
@if (!str($application->status)->startsWith('exited')) @if (!str($application->status)->startsWith('exited'))
@if (!$application->destination->server->isSwarm()) @if (!$application->destination->server->isSwarm())
<x-forms.button title="With rolling update if possible" wire:click='deploy'> <x-forms.button canGate="deploy" :canResource="$application" title="With rolling update if possible" wire:click='deploy'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-orange-400"
viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path <path
d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988"> d="M10.09 4.01l.496 -.495a2 2 0 0 1 2.828 0l7.071 7.07a2 2 0 0 1 0 2.83l-7.07 7.07a2 2 0 0 1 -2.83 0l-7.07 -7.07a2 2 0 0 1 0 -2.83l3.535 -3.535h-3.988">
</path> </path>
<path d="M7.05 11.038v-3.988"></path> <path d="M7.05 11.038v-3.988"></path>
</svg>
Redeploy
</x-forms.button>
@endif
@if ($application->build_pack !== 'dockercompose')
@if ($application->destination->server->isSwarm())
<x-forms.button title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg> </svg>
Update Service Redeploy
</x-forms.button>
@else
<x-forms.button title="Restart without rebuilding" wire:click='restart'>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Restart
</x-forms.button> </x-forms.button>
@endif @endif
@endif @if ($application->build_pack !== 'dockercompose')
<x-modal-confirmation title="Confirm Application Stopping?" buttonTitle="Stop" @if ($application->destination->server->isSwarm())
submitAction="stop" :checkboxes="$checkboxes" :actions="[ <x-forms.button canGate="deploy" :canResource="$application" title="Redeploy Swarm Service (rolling update)" wire:click='deploy'>
'This application will be stopped.', <svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
'All non-persistent data of this application will be deleted.', xmlns="http://www.w3.org/2000/svg">
]" :confirmWithText="false" :confirmWithPassword="false" <g fill="none" stroke="currentColor" stroke-linecap="round"
step1ButtonText="Continue" step2ButtonText="Confirm"> stroke-linejoin="round" stroke-width="2">
<x-slot:button-title> <path
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" <path d="M20 4v5h-5" />
stroke-linejoin="round"> </g>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path> </svg>
<path Update Service
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"> </x-forms.button>
</path> @else
<path <x-forms.button canGate="deploy" :canResource="$application" title="Restart without rebuilding" wire:click='restart'>
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"> <svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
</path> xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round"
stroke-linejoin="round" stroke-width="2">
<path
d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" />
</g>
</svg>
Restart
</x-forms.button>
@endif
@endif
<x-modal-confirmation :disabled="!auth()->user()->can('deploy', $application)" title="Confirm Application Stopping?" buttonTitle="Stop"
submitAction="stop" :checkboxes="$checkboxes" :actions="[
'This application will be stopped.',
'All non-persistent data of this application will be deleted.',
]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg>
Stop
</x-slot:button-title>
</x-modal-confirmation>
@else
<x-forms.button canGate="deploy" :canResource="$application" wire:click='deploy'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg> </svg>
Stop Deploy
</x-slot:button-title> </x-forms.button>
</x-modal-confirmation> @endif
@else </div>
<x-forms.button wire:click='deploy'>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning"
viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none"
stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Deploy
</x-forms.button>
@endif
</div>
@endif @endif
</div> </div>
</div> </div>

View file

@ -38,72 +38,72 @@ class="flex overflow-x-scroll shrink-0 gap-6 items-center whitespace-nowrap sm:o
@endif @endif
</nav> </nav>
@if ($database->destination->server->isFunctional()) @if ($database->destination->server->isFunctional())
<div class="flex flex-wrap gap-2 items-center"> <div class="flex flex-wrap gap-2 items-center">
@if (!str($database->status)->startsWith('exited')) @if (!str($database->status)->startsWith('exited'))
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart" <x-modal-confirmation :disabled="!auth()->user()->can('manage', $database)" title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
:actions="[ :actions="[
'This database will be unavailable during the restart.', 'This database will be unavailable during the restart.',
'If the database is currently in use data could be lost.', 'If the database is currently in use data could be lost.',
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database" ]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
:dispatchEvent="true" dispatchEventType="restartEvent"> :dispatchEvent="true" dispatchEventType="restartEvent">
<x-slot:button-title> <x-slot:button-title>
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" <svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"> stroke-width="2">
<path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" /> <path d="M19.933 13.041a8 8 0 1 1-9.925-8.788c3.899-1 7.935 1.007 9.425 4.747" />
<path d="M20 4v5h-5" /> <path d="M20 4v5h-5" />
</g> </g>
</svg> </svg>
Restart Restart
</x-slot:button-title> </x-slot:button-title>
</x-modal-confirmation> </x-modal-confirmation>
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop" <x-modal-confirmation :disabled="!auth()->user()->can('manage', $database)" title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[ :checkboxes="$checkboxes" :actions="[
'This database will be stopped.', 'This database will be stopped.',
'If the database is currently in use data could be lost.', 'If the database is currently in use data could be lost.',
'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).', 'All non-persistent data of this database (containers, networks, unused images) will be deleted (don\'t worry, no data is lost and you can start the database again).',
]" :confirmWithText="false" :confirmWithPassword="false" ]" :confirmWithText="false" :confirmWithPassword="false"
step1ButtonText="Continue" step2ButtonText="Confirm"> step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title> <x-slot:button-title>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
<path
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg>
Stop
</x-slot:button-title>
</x-modal-confirmation>
@else
<button @disabled(!auth()->user()->can('manage', $database)) @click="$wire.dispatch('startEvent')" class="gap-2 button">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"> stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"> <path d="M7 4v16l13 -8z" />
</path>
<path
d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z">
</path>
</svg> </svg>
Stop Start
</x-slot:button-title> </button>
</x-modal-confirmation> @endif
@else @script
<button @click="$wire.dispatch('startEvent')" class="gap-2 button"> <script>
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" $wire.$on('startEvent', () => {
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" window.dispatchEvent(new CustomEvent('startdatabase'));
stroke-linejoin="round"> $wire.$call('start');
<path stroke="none" d="M0 0h24v24H0z" fill="none" /> });
<path d="M7 4v16l13 -8z" /> $wire.$on('restartEvent', () => {
</svg> $wire.$dispatch('info', 'Restarting database.');
Start window.dispatchEvent(new CustomEvent('startdatabase'));
</button> $wire.$call('restart');
@endif });
@script </script>
<script> @endscript
$wire.$on('startEvent', () => { </div>
window.dispatchEvent(new CustomEvent('startdatabase'));
$wire.$call('start');
});
$wire.$on('restartEvent', () => {
$wire.$dispatch('info', 'Restarting database.');
window.dispatchEvent(new CustomEvent('startdatabase'));
$wire.$call('restart');
});
</script>
@endscript
</div>
@else @else
<div class="text-error">Underlying server is not functional.</div> <div class="text-error">Underlying server is not functional.</div>
@endif @endif

View file

@ -30,7 +30,7 @@
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last"> <div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
<x-services.advanced :service="$service" /> <x-services.advanced :service="$service" />
@if (str($service->status)->contains('running')) @if (str($service->status)->contains('running'))
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')"> <x-forms.button canGate="deploy" :canResource="$service" title="Restart" @click="$wire.dispatch('restartEvent')">
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"> stroke-width="2">
@ -40,7 +40,7 @@
</svg> </svg>
Restart Restart
</x-forms.button> </x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true" <x-modal-confirmation :disabled="!auth()->user()->can('stop', $service)" title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm"> :confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title> <x-slot:button-title>
@ -58,7 +58,7 @@
</x-slot:button-title> </x-slot:button-title>
</x-modal-confirmation> </x-modal-confirmation>
@elseif (str($service->status)->contains('degraded')) @elseif (str($service->status)->contains('degraded'))
<x-forms.button title="Restart" @click="$wire.dispatch('restartEvent')"> <x-forms.button canGate="deploy" :canResource="$service" title="Restart" @click="$wire.dispatch('restartEvent')">
<svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> <svg class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"> stroke-width="2">
@ -68,7 +68,7 @@
</svg> </svg>
Restart Restart
</x-forms.button> </x-forms.button>
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true" <x-modal-confirmation :disabled="!auth()->user()->can('stop', $service)" title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm"> :confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title> <x-slot:button-title>
@ -86,7 +86,7 @@
</x-slot:button-title> </x-slot:button-title>
</x-modal-confirmation> </x-modal-confirmation>
@elseif (str($service->status)->contains('exited')) @elseif (str($service->status)->contains('exited'))
<button @click="$wire.dispatch('startEvent')" class="gap-2 button"> <button @disabled(!auth()->user()->can('deploy', $service)) @click="$wire.dispatch('startEvent')" class="gap-2 button">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"> stroke-linejoin="round">
@ -96,7 +96,7 @@
Deploy Deploy
</button> </button>
@else @else
<x-modal-confirmation title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true" <x-modal-confirmation :disabled="!auth()->user()->can('stop', $service)" title="Confirm Service Stopping?" buttonTitle="Stop" :dispatchEvent="true"
submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]" submitAction="stop" dispatchEventType="stopEvent" :checkboxes="$checkboxes" :actions="[__('service.stop'), __('resource.non_persistent')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm"> :confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title> <x-slot:button-title>
@ -113,7 +113,7 @@
Stop Stop
</x-slot:button-title> </x-slot:button-title>
</x-modal-confirmation> </x-modal-confirmation>
<button @click="$wire.dispatch('startEvent')" class="gap-2 button"> <button @disabled(!auth()->user()->can('deploy', $service)) @click="$wire.dispatch('startEvent')" class="gap-2 button">
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24" <svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 dark:text-warning" viewBox="0 0 24 24"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round"> stroke-linejoin="round">

View file

@ -3,12 +3,12 @@
<h2>Healthchecks</h2> <h2>Healthchecks</h2>
<x-forms.button canGate="update" :canResource="$resource" type="submit">Save</x-forms.button> <x-forms.button canGate="update" :canResource="$resource" type="submit">Save</x-forms.button>
@if (!$healthCheckEnabled) @if (!$healthCheckEnabled)
<x-modal-confirmation title="Confirm Healthcheck Enable?" buttonTitle="Enable Healthcheck" <x-modal-confirmation :disabled="!auth()->user()->can('update', $resource)" title="Confirm Healthcheck Enable?" buttonTitle="Enable Healthcheck"
submitAction="toggleHealthcheck" :actions="['Enable healthcheck for this resource.']" submitAction="toggleHealthcheck" :actions="['Enable healthcheck for this resource.']"
warningMessage="If the health check fails, your application will become inaccessible. Please review the <a href='https://coolify.io/docs/knowledge-base/health-checks' target='_blank' class='underline text-white'>Health Checks</a> guide before proceeding!" warningMessage="If the health check fails, your application will become inaccessible. Please review the <a href='https://coolify.io/docs/knowledge-base/health-checks' target='_blank' class='underline text-white'>Health Checks</a> guide before proceeding!"
step2ButtonText="Enable Healthcheck" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Enable Healthcheck" :confirmWithText="false" :confirmWithPassword="false"
isHighlightedButton> isHighlightedButton>
</x-modal-confirmation> </x-modal-confirmation>
@else @else
<x-forms.button canGate="update" :canResource="$resource" wire:click="toggleHealthcheck">Disable Healthcheck</x-forms.button> <x-forms.button canGate="update" :canResource="$resource" wire:click="toggleHealthcheck">Disable Healthcheck</x-forms.button>
@endif @endif

View file

@ -50,8 +50,13 @@ class="pr-1">:</span>
helper="Write access requires admin or owner role" :checked="false"></x-forms.checkbox> helper="Write access requires admin or owner role" :checked="false"></x-forms.checkbox>
@endif @endif
<x-forms.checkbox label="deploy" wire:model.live="permissions" domValue="deploy" @if ($canUseDeployPermissions)
helper="Can trigger deploy webhooks." :checked="in_array('deploy', $permissions)"></x-forms.checkbox> <x-forms.checkbox label="deploy" wire:model.live="permissions" domValue="deploy"
helper="Can trigger deploy webhooks." :checked="in_array('deploy', $permissions)"></x-forms.checkbox>
@else
<x-forms.checkbox label="deploy (admin/owner only)" disabled domValue="deploy"
helper="Deploy access requires admin or owner role" :checked="false"></x-forms.checkbox>
@endif
<x-forms.checkbox label="read" domValue="read" wire:model.live="permissions" domValue="read" <x-forms.checkbox label="read" domValue="read" wire:model.live="permissions" domValue="read"
:checked="in_array('read', $permissions)"></x-forms.checkbox> :checked="in_array('read', $permissions)"></x-forms.checkbox>
<x-forms.checkbox label="read:sensitive" wire:model.live="permissions" domValue="read:sensitive" <x-forms.checkbox label="read:sensitive" wire:model.live="permissions" domValue="read:sensitive"

View file

@ -62,7 +62,7 @@ class="mx-1 dark:hover:fill-white fill-black dark:fill-warning">
<div class="subtitle">{{ data_get($server, 'name') }}</div> <div class="subtitle">{{ data_get($server, 'name') }}</div>
<div class="navbar-main"> <div class="navbar-main">
<nav <nav
class="flex items-center gap-4 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap pt-2"> class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap pt-2">
<a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}" href="{{ route('server.show', [ <a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}" href="{{ route('server.show', [
'server_uuid' => data_get($server, 'uuid'), 'server_uuid' => data_get($server, 'uuid'),
]) }}" {{ wireNavigate() }}> ]) }}" {{ wireNavigate() }}>

View file

@ -134,22 +134,22 @@
{{ data_get($resource, 'State') }} {{ data_get($resource, 'State') }}
</td> </td>
<td class="flex gap-2 px-5 py-4 text-sm whitespace-nowrap"> <td class="flex gap-2 px-5 py-4 text-sm whitespace-nowrap">
@if (data_get($resource, 'State') === 'running') @if (data_get($resource, 'State') === 'running')
<x-forms.button <x-forms.button canGate="update" :canResource="$server"
wire:click="restartUnmanaged('{{ data_get($resource, 'ID') }}')" wire:click="restartUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Restart</x-forms.button> wire:key="{{ data_get($resource, 'ID') }}">Restart</x-forms.button>
<x-forms.button isError <x-forms.button canGate="update" :canResource="$server" isError
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')" wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button> wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@elseif (data_get($resource, 'State') === 'exited') @elseif (data_get($resource, 'State') === 'exited')
<x-forms.button <x-forms.button canGate="update" :canResource="$server"
wire:click="startUnmanaged('{{ data_get($resource, 'ID') }}')" wire:click="startUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Start</x-forms.button> wire:key="{{ data_get($resource, 'ID') }}">Start</x-forms.button>
@elseif (data_get($resource, 'State') === 'restarting') @elseif (data_get($resource, 'State') === 'restarting')
<x-forms.button <x-forms.button canGate="update" :canResource="$server"
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')" wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button> wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@endif @endif
</td> </td>
</tr> </tr>
@endforeach @endforeach

11
tasks/lessons.md Normal file
View file

@ -0,0 +1,11 @@
# Lessons Learned
## Docker / Worktree Setup
- The Docker dev container mounts from `young-stork` worktree, NOT `ivory-raccoon`
- Do NOT copy files to `young-stork` or use `docker cp` — only modify files in the `ivory-raccoon` worktree
- Do NOT use `docker exec` to run tests — work entirely within the `ivory-raccoon` worktree
## Policy Tests
- Policy methods have typed parameters (e.g., `Server $server`) — anonymous classes cause TypeError
- Must use `Mockery::mock(Model::class)->makePartial()` instead of anonymous classes for model stubs
- Use `shouldReceive('getAttribute')->with('property')->andReturn(value)` for model properties accessed via relationship chains

View file

@ -156,6 +156,54 @@
}); });
}); });
describe('create team', function () {
test('member can create a new independent team', function () {
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
$newTeam = Team::create([
'name' => 'New Team',
'description' => 'Created by member',
'personal_team' => false,
]);
expect($newTeam)->toBeInstanceOf(Team::class)
->and($newTeam->name)->toBe('New Team');
});
test('member cannot update an existing team', function () {
$this->actingAs($this->member);
session(['currentTeam' => $this->team]);
expect(fn () => $this->team->update(['name' => 'Hacked']))
->toThrow(\Exception::class, 'You are not allowed to update this team.');
});
test('owner can create a new team', function () {
$this->actingAs($this->owner);
session(['currentTeam' => $this->team]);
$newTeam = Team::create([
'name' => 'Owner New Team',
'personal_team' => false,
]);
expect($newTeam)->toBeInstanceOf(Team::class);
});
test('admin can create a new team', function () {
$this->actingAs($this->admin);
session(['currentTeam' => $this->team]);
$newTeam = Team::create([
'name' => 'Admin New Team',
'personal_team' => false,
]);
expect($newTeam)->toBeInstanceOf(Team::class);
});
});
describe('view permission', function () { describe('view permission', function () {
test('owner can view team', function () { test('owner can view team', function () {
$this->actingAs($this->owner); $this->actingAs($this->owner);

View file

@ -0,0 +1,167 @@
<?php
use App\Models\User;
use App\Policies\ApiTokenPolicy;
use Laravel\Sanctum\PersonalAccessToken;
it('allows any user to view any api tokens', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ApiTokenPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows any user to create api tokens', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ApiTokenPolicy;
expect($policy->create($user))->toBeTrue();
});
it('allows any user to manage api tokens', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ApiTokenPolicy;
expect($policy->manage($user))->toBeTrue();
});
it('allows owner to view their own api token', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->id = 1;
$token = Mockery::mock(PersonalAccessToken::class)->makePartial();
$token->tokenable_id = 1;
$token->tokenable_type = User::class;
$policy = new ApiTokenPolicy;
expect($policy->view($user, $token))->toBeTrue();
});
it('denies non-owner from viewing api token', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->id = 2;
$token = Mockery::mock(PersonalAccessToken::class)->makePartial();
$token->tokenable_id = 1;
$token->tokenable_type = User::class;
$policy = new ApiTokenPolicy;
expect($policy->view($user, $token))->toBeFalse();
});
it('allows owner to update their own api token', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->id = 1;
$token = Mockery::mock(PersonalAccessToken::class)->makePartial();
$token->tokenable_id = 1;
$token->tokenable_type = User::class;
$policy = new ApiTokenPolicy;
expect($policy->update($user, $token))->toBeTrue();
});
it('denies non-owner from updating api token', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->id = 2;
$token = Mockery::mock(PersonalAccessToken::class)->makePartial();
$token->tokenable_id = 1;
$token->tokenable_type = User::class;
$policy = new ApiTokenPolicy;
expect($policy->update($user, $token))->toBeFalse();
});
it('allows owner to delete their own api token', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->id = 1;
$token = Mockery::mock(PersonalAccessToken::class)->makePartial();
$token->tokenable_id = 1;
$token->tokenable_type = User::class;
$policy = new ApiTokenPolicy;
expect($policy->delete($user, $token))->toBeTrue();
});
it('denies non-owner from deleting api token', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->id = 2;
$token = Mockery::mock(PersonalAccessToken::class)->makePartial();
$token->tokenable_id = 1;
$token->tokenable_type = User::class;
$policy = new ApiTokenPolicy;
expect($policy->delete($user, $token))->toBeFalse();
});
it('allows admin to use root permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ApiTokenPolicy;
expect($policy->useRootPermissions($user))->toBeTrue();
});
it('allows owner to use root permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$user->shouldReceive('isOwner')->andReturn(true);
$policy = new ApiTokenPolicy;
expect($policy->useRootPermissions($user))->toBeTrue();
});
it('denies member from using root permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$user->shouldReceive('isOwner')->andReturn(false);
$policy = new ApiTokenPolicy;
expect($policy->useRootPermissions($user))->toBeFalse();
});
it('allows admin to use write permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ApiTokenPolicy;
expect($policy->useWritePermissions($user))->toBeTrue();
});
it('denies member from using write permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$user->shouldReceive('isOwner')->andReturn(false);
$policy = new ApiTokenPolicy;
expect($policy->useWritePermissions($user))->toBeFalse();
});
it('allows admin to use deploy permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ApiTokenPolicy;
expect($policy->useDeployPermissions($user))->toBeTrue();
});
it('allows owner to use deploy permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$user->shouldReceive('isOwner')->andReturn(true);
$policy = new ApiTokenPolicy;
expect($policy->useDeployPermissions($user))->toBeTrue();
});
it('denies member from using deploy permissions', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$user->shouldReceive('isOwner')->andReturn(false);
$policy = new ApiTokenPolicy;
expect($policy->useDeployPermissions($user))->toBeFalse();
});

View file

@ -0,0 +1,237 @@
<?php
use App\Models\Application;
use App\Models\User;
use App\Policies\ApplicationPolicy;
it('allows any user to view any applications', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ApplicationPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their own team application', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->view($user, $application))->toBeTrue();
});
it('denies non-member to view another team application', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 2]);
$policy = new ApplicationPolicy;
expect($policy->view($user, $application))->toBeFalse();
});
it('denies view when application has no team', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn(null);
$policy = new ApplicationPolicy;
expect($policy->view($user, $application))->toBeFalse();
});
it('allows admin to create an application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ApplicationPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member to create an application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ApplicationPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update their own team application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->update($user, $application)->allowed())->toBeTrue();
});
it('denies team member to update their own team application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->update($user, $application)->allowed())->toBeFalse();
});
it('denies update when application has no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn(null);
$policy = new ApplicationPolicy;
expect($policy->update($user, $application)->allowed())->toBeFalse();
});
it('allows team admin to delete their own team application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->delete($user, $application))->toBeTrue();
});
it('denies team member to delete their own team application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->delete($user, $application))->toBeFalse();
});
it('denies delete when application has no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn(null);
$policy = new ApplicationPolicy;
expect($policy->delete($user, $application))->toBeFalse();
});
it('allows team admin to deploy their own team application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->deploy($user, $application))->toBeTrue();
});
it('denies team member to deploy their own team application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->deploy($user, $application))->toBeFalse();
});
it('allows team admin to manage deployments', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->manageDeployments($user, $application))->toBeTrue();
});
it('denies team member to manage deployments', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->manageDeployments($user, $application))->toBeFalse();
});
it('allows team admin to manage environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->manageEnvironment($user, $application))->toBeTrue();
});
it('denies team member to manage environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ApplicationPolicy;
expect($policy->manageEnvironment($user, $application))->toBeFalse();
});
it('allows admin to cleanup deployment queue', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ApplicationPolicy;
expect($policy->cleanupDeploymentQueue($user))->toBeTrue();
});
it('denies member to cleanup deployment queue', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ApplicationPolicy;
expect($policy->cleanupDeploymentQueue($user))->toBeFalse();
});
it('denies restore for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$application = Mockery::mock(Application::class)->makePartial();
$policy = new ApplicationPolicy;
expect($policy->restore($user, $application))->toBeFalse();
});
it('denies force delete for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$application = Mockery::mock(Application::class)->makePartial();
$policy = new ApplicationPolicy;
expect($policy->forceDelete($user, $application))->toBeFalse();
});

View file

@ -0,0 +1,239 @@
<?php
use App\Models\Application;
use App\Models\ApplicationPreview;
use App\Models\User;
use App\Policies\ApplicationPreviewPolicy;
it('allows any user to view any application previews', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ApplicationPreviewPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view application preview', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->view($user, $preview))->toBeTrue();
});
it('denies non-team member from viewing application preview', function () {
$teams = collect([
(object) ['id' => 2, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->view($user, $preview))->toBeFalse();
});
it('denies viewing application preview with null application', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'admin']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn(null);
$policy = new ApplicationPreviewPolicy;
expect($policy->view($user, $preview))->toBeFalse();
});
it('allows admin user to create application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ApplicationPreviewPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies non-admin user from creating application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ApplicationPreviewPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->update($user, $preview)->allowed())->toBeTrue();
});
it('denies team member from updating application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->update($user, $preview)->allowed())->toBeFalse();
});
it('denies updating application preview with null application', function () {
$user = Mockery::mock(User::class)->makePartial();
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn(null);
$policy = new ApplicationPreviewPolicy;
$response = $policy->update($user, $preview);
expect($response->allowed())->toBeFalse();
});
it('allows team admin to delete application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->delete($user, $preview))->toBeTrue();
});
it('denies team member from deleting application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->delete($user, $preview))->toBeFalse();
});
it('denies deleting application preview with null application', function () {
$user = Mockery::mock(User::class)->makePartial();
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn(null);
$policy = new ApplicationPreviewPolicy;
expect($policy->delete($user, $preview))->toBeFalse();
});
it('allows team admin to deploy application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->deploy($user, $preview))->toBeTrue();
});
it('denies team member from deploying application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->deploy($user, $preview))->toBeFalse();
});
it('allows team admin to manage preview deployments', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->manageDeployments($user, $preview))->toBeTrue();
});
it('denies team member from managing preview deployments', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$preview->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationPreviewPolicy;
expect($policy->manageDeployments($user, $preview))->toBeFalse();
});
it('denies restoring application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$policy = new ApplicationPreviewPolicy;
expect($policy->restore($user, $preview))->toBeFalse();
});
it('denies force deleting application preview', function () {
$user = Mockery::mock(User::class)->makePartial();
$preview = Mockery::mock(ApplicationPreview::class)->makePartial();
$policy = new ApplicationPreviewPolicy;
expect($policy->forceDelete($user, $preview))->toBeFalse();
});

View file

@ -0,0 +1,178 @@
<?php
use App\Models\Application;
use App\Models\ApplicationSetting;
use App\Models\User;
use App\Policies\ApplicationSettingPolicy;
it('allows any user to view any application settings', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ApplicationSettingPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view application setting', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationSettingPolicy;
expect($policy->view($user, $setting))->toBeTrue();
});
it('denies non-team member from viewing application setting', function () {
$teams = collect([
(object) ['id' => 2, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationSettingPolicy;
expect($policy->view($user, $setting))->toBeFalse();
});
it('denies viewing application setting with null application', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'admin']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn(null);
$policy = new ApplicationSettingPolicy;
expect($policy->view($user, $setting))->toBeFalse();
});
it('allows admin user to create application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ApplicationSettingPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies non-admin user from creating application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ApplicationSettingPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationSettingPolicy;
expect($policy->update($user, $setting))->toBeTrue();
});
it('denies team member from updating application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationSettingPolicy;
expect($policy->update($user, $setting))->toBeFalse();
});
it('denies updating application setting with null application', function () {
$user = Mockery::mock(User::class)->makePartial();
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn(null);
$policy = new ApplicationSettingPolicy;
expect($policy->update($user, $setting))->toBeFalse();
});
it('allows team admin to delete application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationSettingPolicy;
expect($policy->delete($user, $setting))->toBeTrue();
});
it('denies team member from deleting application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$team = (object) ['id' => 1];
$application = Mockery::mock(Application::class)->makePartial();
$application->shouldReceive('team')->andReturn($team);
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn($application);
$policy = new ApplicationSettingPolicy;
expect($policy->delete($user, $setting))->toBeFalse();
});
it('denies deleting application setting with null application', function () {
$user = Mockery::mock(User::class)->makePartial();
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$setting->shouldReceive('getAttribute')->with('application')->andReturn(null);
$policy = new ApplicationSettingPolicy;
expect($policy->delete($user, $setting))->toBeFalse();
});
it('denies restoring application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$policy = new ApplicationSettingPolicy;
expect($policy->restore($user, $setting))->toBeFalse();
});
it('denies force deleting application setting', function () {
$user = Mockery::mock(User::class)->makePartial();
$setting = Mockery::mock(ApplicationSetting::class)->makePartial();
$policy = new ApplicationSettingPolicy;
expect($policy->forceDelete($user, $setting))->toBeFalse();
});

View file

@ -0,0 +1,221 @@
<?php
use App\Models\StandalonePostgresql;
use App\Models\User;
use App\Policies\DatabasePolicy;
it('allows any user to view any databases', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new DatabasePolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their own team database', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->view($user, $database))->toBeTrue();
});
it('denies non-member to view another team database', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 2]);
$policy = new DatabasePolicy;
expect($policy->view($user, $database))->toBeFalse();
});
it('denies view when database has no team', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn(null);
$policy = new DatabasePolicy;
expect($policy->view($user, $database))->toBeFalse();
});
it('allows admin to create a database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new DatabasePolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member to create a database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new DatabasePolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update their own team database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->update($user, $database)->allowed())->toBeTrue();
});
it('denies team member to update their own team database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->update($user, $database)->allowed())->toBeFalse();
});
it('denies update when database has no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn(null);
$policy = new DatabasePolicy;
expect($policy->update($user, $database)->allowed())->toBeFalse();
});
it('allows team admin to delete their own team database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->delete($user, $database))->toBeTrue();
});
it('denies team member to delete their own team database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->delete($user, $database))->toBeFalse();
});
it('denies delete when database has no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn(null);
$policy = new DatabasePolicy;
expect($policy->delete($user, $database))->toBeFalse();
});
it('allows team admin to manage their own team database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->manage($user, $database))->toBeTrue();
});
it('denies team member to manage their own team database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->manage($user, $database))->toBeFalse();
});
it('allows team admin to manage backups', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->manageBackups($user, $database))->toBeTrue();
});
it('denies team member to manage backups', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->manageBackups($user, $database))->toBeFalse();
});
it('allows team admin to manage database environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->manageEnvironment($user, $database))->toBeTrue();
});
it('denies team member to manage database environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$database->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new DatabasePolicy;
expect($policy->manageEnvironment($user, $database))->toBeFalse();
});
it('denies restore for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$policy = new DatabasePolicy;
expect($policy->restore($user, $database))->toBeFalse();
});
it('denies force delete for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$database = Mockery::mock(StandalonePostgresql::class)->makePartial();
$policy = new DatabasePolicy;
expect($policy->forceDelete($user, $database))->toBeFalse();
});

View file

@ -0,0 +1,155 @@
<?php
use App\Models\Environment;
use App\Models\User;
use App\Policies\EnvironmentPolicy;
it('allows any user to view any environments', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new EnvironmentPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their own team environment', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn((object) ['team_id' => 1]);
$policy = new EnvironmentPolicy;
expect($policy->view($user, $environment))->toBeTrue();
});
it('denies non-member to view another team environment', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn((object) ['team_id' => 2]);
$policy = new EnvironmentPolicy;
expect($policy->view($user, $environment))->toBeFalse();
});
it('denies view when environment has no project', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn(null);
$policy = new EnvironmentPolicy;
expect($policy->view($user, $environment))->toBeFalse();
});
it('allows admin to create an environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new EnvironmentPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member to create an environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new EnvironmentPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update their own team environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn((object) ['team_id' => 1]);
$policy = new EnvironmentPolicy;
expect($policy->update($user, $environment))->toBeTrue();
});
it('denies team member to update their own team environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn((object) ['team_id' => 1]);
$policy = new EnvironmentPolicy;
expect($policy->update($user, $environment))->toBeFalse();
});
it('denies update when environment has no project', function () {
$user = Mockery::mock(User::class)->makePartial();
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn(null);
$policy = new EnvironmentPolicy;
expect($policy->update($user, $environment))->toBeFalse();
});
it('allows team admin to delete their own team environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn((object) ['team_id' => 1]);
$policy = new EnvironmentPolicy;
expect($policy->delete($user, $environment))->toBeTrue();
});
it('denies team member to delete their own team environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn((object) ['team_id' => 1]);
$policy = new EnvironmentPolicy;
expect($policy->delete($user, $environment))->toBeFalse();
});
it('denies delete when environment has no project', function () {
$user = Mockery::mock(User::class)->makePartial();
$environment = Mockery::mock(Environment::class)->makePartial();
$environment->shouldReceive('getAttribute')->with('project')->andReturn(null);
$policy = new EnvironmentPolicy;
expect($policy->delete($user, $environment))->toBeFalse();
});
it('denies restore for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$environment = Mockery::mock(Environment::class)->makePartial();
$policy = new EnvironmentPolicy;
expect($policy->restore($user, $environment))->toBeFalse();
});
it('denies force delete for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$environment = Mockery::mock(Environment::class)->makePartial();
$policy = new EnvironmentPolicy;
expect($policy->forceDelete($user, $environment))->toBeFalse();
});

View file

@ -0,0 +1,199 @@
<?php
use App\Models\Application;
use App\Models\EnvironmentVariable;
use App\Models\User;
use App\Policies\EnvironmentVariablePolicy;
use Illuminate\Support\Collection;
it('allows any user to view any environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new EnvironmentVariablePolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->teams = new Collection([(object) ['id' => 1]]);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->view($user, $envVar))->toBeTrue();
});
it('denies non-team member from viewing environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->teams = new Collection([(object) ['id' => 2]]);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->view($user, $envVar))->toBeFalse();
});
it('denies view when resourceable is null', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->teams = new Collection([(object) ['id' => 1]]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn(null);
$policy = new EnvironmentVariablePolicy;
expect($policy->view($user, $envVar))->toBeFalse();
});
it('allows admin to create environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new EnvironmentVariablePolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member from creating environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new EnvironmentVariablePolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->update($user, $envVar))->toBeTrue();
});
it('denies non-admin from updating environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->update($user, $envVar))->toBeFalse();
});
it('denies update when resourceable is null', function () {
$user = Mockery::mock(User::class)->makePartial();
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn(null);
$policy = new EnvironmentVariablePolicy;
expect($policy->update($user, $envVar))->toBeFalse();
});
it('allows team admin to delete environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->delete($user, $envVar))->toBeTrue();
});
it('denies non-admin from deleting environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->delete($user, $envVar))->toBeFalse();
});
it('denies delete when resourceable is null', function () {
$user = Mockery::mock(User::class)->makePartial();
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn(null);
$policy = new EnvironmentVariablePolicy;
expect($policy->delete($user, $envVar))->toBeFalse();
});
it('denies restore for environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$policy = new EnvironmentVariablePolicy;
expect($policy->restore($user, $envVar))->toBeFalse();
});
it('denies force delete for environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$policy = new EnvironmentVariablePolicy;
expect($policy->forceDelete($user, $envVar))->toBeFalse();
});
it('allows team admin to manage environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->manageEnvironment($user, $envVar))->toBeTrue();
});
it('denies non-admin from managing environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$resource = Mockery::mock(Application::class)->makePartial();
$resource->shouldReceive('team')->andReturn((object) ['id' => 1]);
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn($resource);
$policy = new EnvironmentVariablePolicy;
expect($policy->manageEnvironment($user, $envVar))->toBeFalse();
});
it('denies manage environment when resourceable is null', function () {
$user = Mockery::mock(User::class)->makePartial();
$envVar = Mockery::mock(EnvironmentVariable::class)->makePartial();
$envVar->shouldReceive('getAttribute')->with('resourceable')->andReturn(null);
$policy = new EnvironmentVariablePolicy;
expect($policy->manageEnvironment($user, $envVar))->toBeFalse();
});

View file

@ -0,0 +1,189 @@
<?php
use App\Models\GithubApp;
use App\Models\User;
use App\Policies\GithubAppPolicy;
it('allows any user to view any github apps', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new GithubAppPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows any user to view system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = true;
$policy = new GithubAppPolicy;
expect($policy->view($user, $model))->toBeTrue();
});
it('allows team member to view non-system-wide github app', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->view($user, $model))->toBeTrue();
});
it('denies non-team member to view non-system-wide github app', function () {
$teams = collect([
(object) ['id' => 2, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->view($user, $model))->toBeFalse();
});
it('allows admin to create github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new GithubAppPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies non-admin to create github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new GithubAppPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows user with system access to update system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('canAccessSystemResources')->andReturn(true);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = true;
$policy = new GithubAppPolicy;
expect($policy->update($user, $model))->toBeTrue();
});
it('denies user without system access to update system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('canAccessSystemResources')->andReturn(false);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = true;
$policy = new GithubAppPolicy;
expect($policy->update($user, $model))->toBeFalse();
});
it('allows team admin to update non-system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->update($user, $model))->toBeTrue();
});
it('denies team member to update non-system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->update($user, $model))->toBeFalse();
});
it('allows user with system access to delete system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('canAccessSystemResources')->andReturn(true);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = true;
$policy = new GithubAppPolicy;
expect($policy->delete($user, $model))->toBeTrue();
});
it('denies user without system access to delete system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('canAccessSystemResources')->andReturn(false);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = true;
$policy = new GithubAppPolicy;
expect($policy->delete($user, $model))->toBeFalse();
});
it('allows team admin to delete non-system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->delete($user, $model))->toBeTrue();
});
it('denies team member to delete non-system-wide github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->delete($user, $model))->toBeFalse();
});
it('denies restore of github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->restore($user, $model))->toBeFalse();
});
it('denies force delete of github app', function () {
$user = Mockery::mock(User::class)->makePartial();
$model = Mockery::mock(GithubApp::class)->makePartial();
$model->team_id = 1;
$model->is_system_wide = false;
$policy = new GithubAppPolicy;
expect($policy->forceDelete($user, $model))->toBeFalse();
});

View file

@ -0,0 +1,175 @@
<?php
use App\Models\User;
use App\Policies\NotificationPolicy;
use Illuminate\Database\Eloquent\Model;
it('allows team member to view notification settings', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->view($user, $notification))->toBeTrue();
});
it('denies non-team member from viewing notification settings', function () {
$teams = collect([
(object) ['id' => 2, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->view($user, $notification))->toBeFalse();
});
it('denies viewing notification settings with no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn(null);
$policy = new NotificationPolicy;
expect($policy->view($user, $notification))->toBeFalse();
});
it('allows team admin to update notification settings', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->update($user, $notification))->toBeTrue();
});
it('denies team member from updating notification settings', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->update($user, $notification))->toBeFalse();
});
it('denies updating notification settings with no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn(null);
$policy = new NotificationPolicy;
expect($policy->update($user, $notification))->toBeFalse();
});
it('allows team admin to manage notification settings', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->manage($user, $notification))->toBeTrue();
});
it('denies team member from managing notification settings', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->manage($user, $notification))->toBeFalse();
});
it('denies managing notification settings with no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn(null);
$policy = new NotificationPolicy;
expect($policy->manage($user, $notification))->toBeFalse();
});
it('allows team admin to send test notification', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->sendTest($user, $notification))->toBeTrue();
});
it('denies team member from sending test notification', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->sendTest($user, $notification))->toBeFalse();
});
it('denies sending test notification with no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn(null);
$policy = new NotificationPolicy;
expect($policy->sendTest($user, $notification))->toBeFalse();
});
it('allows team member to view but not update notification settings', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->view($user, $notification))->toBeTrue();
expect($policy->update($user, $notification))->toBeFalse();
});
it('allows team admin to view and update notification settings', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'admin']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$notification = Mockery::mock(Model::class)->makePartial();
$notification->shouldReceive('getAttribute')->with('team')->andReturn((object) ['id' => 1]);
$policy = new NotificationPolicy;
expect($policy->view($user, $notification))->toBeTrue();
expect($policy->update($user, $notification))->toBeTrue();
});

View file

@ -1,5 +1,6 @@
<?php <?php
use App\Models\PrivateKey;
use App\Models\User; use App\Models\User;
use App\Policies\PrivateKeyPolicy; use App\Policies\PrivateKeyPolicy;
@ -11,10 +12,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeTrue(); expect($policy->view($user, $privateKey))->toBeTrue();
@ -28,10 +27,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeTrue(); expect($policy->view($user, $privateKey))->toBeTrue();
@ -45,10 +42,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeFalse(); expect($policy->view($user, $privateKey))->toBeFalse();
@ -62,10 +57,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeFalse(); expect($policy->view($user, $privateKey))->toBeFalse();
@ -79,10 +72,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 1;
public $team_id = 1;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeTrue(); expect($policy->view($user, $privateKey))->toBeTrue();
@ -96,10 +87,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 2;
public $team_id = 2;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeFalse(); expect($policy->view($user, $privateKey))->toBeFalse();
@ -113,10 +102,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeTrue(); expect($policy->update($user, $privateKey))->toBeTrue();
@ -130,10 +117,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeFalse(); expect($policy->update($user, $privateKey))->toBeFalse();
@ -147,10 +132,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 1;
public $team_id = 1;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeTrue(); expect($policy->update($user, $privateKey))->toBeTrue();
@ -164,10 +147,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 1;
public $team_id = 1;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeFalse(); expect($policy->update($user, $privateKey))->toBeFalse();
@ -181,10 +162,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->delete($user, $privateKey))->toBeTrue(); expect($policy->delete($user, $privateKey))->toBeTrue();
@ -198,10 +177,8 @@
$user = Mockery::mock(User::class)->makePartial(); $user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams); $user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class $privateKey = Mockery::mock(PrivateKey::class)->makePartial();
{ $privateKey->team_id = 0;
public $team_id = 0;
};
$policy = new PrivateKeyPolicy; $policy = new PrivateKeyPolicy;
expect($policy->delete($user, $privateKey))->toBeFalse(); expect($policy->delete($user, $privateKey))->toBeFalse();

View file

@ -0,0 +1,122 @@
<?php
use App\Models\Project;
use App\Models\User;
use App\Policies\ProjectPolicy;
it('allows any user to view any projects', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ProjectPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their own team project', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 1;
$policy = new ProjectPolicy;
expect($policy->view($user, $project))->toBeTrue();
});
it('denies non-member to view another team project', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 2;
$policy = new ProjectPolicy;
expect($policy->view($user, $project))->toBeFalse();
});
it('allows admin to create a project', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ProjectPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member to create a project', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ProjectPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update their own team project', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 1;
$policy = new ProjectPolicy;
expect($policy->update($user, $project))->toBeTrue();
});
it('denies team member to update their own team project', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 1;
$policy = new ProjectPolicy;
expect($policy->update($user, $project))->toBeFalse();
});
it('allows team admin to delete their own team project', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 1;
$policy = new ProjectPolicy;
expect($policy->delete($user, $project))->toBeTrue();
});
it('denies team member to delete their own team project', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 1;
$policy = new ProjectPolicy;
expect($policy->delete($user, $project))->toBeFalse();
});
it('denies restore for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 1;
$policy = new ProjectPolicy;
expect($policy->restore($user, $project))->toBeFalse();
});
it('denies force delete for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$project = Mockery::mock(Project::class)->makePartial();
$project->team_id = 1;
$policy = new ProjectPolicy;
expect($policy->forceDelete($user, $project))->toBeFalse();
});

View file

@ -0,0 +1,61 @@
<?php
use App\Models\Application;
use App\Models\User;
use App\Policies\ResourceCreatePolicy;
it('allows admin to create any resource', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ResourceCreatePolicy;
expect($policy->createAny($user))->toBeTrue();
});
it('denies member from creating any resource', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ResourceCreatePolicy;
expect($policy->createAny($user))->toBeFalse();
});
it('allows admin to create a valid resource class', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ResourceCreatePolicy;
expect($policy->create($user, Application::class))->toBeTrue();
});
it('denies member from creating a valid resource class', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ResourceCreatePolicy;
expect($policy->create($user, Application::class))->toBeFalse();
});
it('denies admin from creating an invalid resource class', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ResourceCreatePolicy;
expect($policy->create($user, 'App\Models\NonExistent'))->toBeFalse();
});
it('allows admin to authorize all resource creation', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ResourceCreatePolicy;
expect($policy->authorizeAllResourceCreation($user))->toBeTrue();
});
it('denies member from authorizing all resource creation', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ResourceCreatePolicy;
expect($policy->authorizeAllResourceCreation($user))->toBeFalse();
});

View file

@ -0,0 +1,157 @@
<?php
use App\Models\Server;
use App\Models\User;
use App\Policies\ServerPolicy;
it('allows any user to view any servers', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ServerPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their own team server', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->view($user, $server))->toBeTrue();
});
it('denies non-member to view another team server', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 2;
$policy = new ServerPolicy;
expect($policy->view($user, $server))->toBeFalse();
});
it('allows admin to create a server', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ServerPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member to create a server', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ServerPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update their own team server', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->update($user, $server))->toBeTrue();
});
it('denies team member to update their own team server', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->update($user, $server))->toBeFalse();
});
it('allows team admin to delete their own team server', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->delete($user, $server))->toBeTrue();
});
it('denies team member to delete their own team server', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->delete($user, $server))->toBeFalse();
});
it('denies restore for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->restore($user, $server))->toBeFalse();
});
it('denies force delete for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->forceDelete($user, $server))->toBeFalse();
});
it('allows team admin to manage proxy', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->manageProxy($user, $server))->toBeTrue();
});
it('denies team member to manage proxy', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->manageProxy($user, $server))->toBeFalse();
});
it('allows team admin to manage sentinel, ca certificate, and view security', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$server = Mockery::mock(Server::class)->makePartial();
$server->team_id = 1;
$policy = new ServerPolicy;
expect($policy->manageSentinel($user, $server))->toBeTrue();
expect($policy->manageCaCertificate($user, $server))->toBeTrue();
expect($policy->viewSecurity($user, $server))->toBeTrue();
});

View file

@ -0,0 +1,37 @@
<?php
use App\Models\ServiceApplication;
use App\Models\User;
use App\Policies\ServiceApplicationPolicy;
it('allows admin to create service application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ServiceApplicationPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member from creating service application', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ServiceApplicationPolicy;
expect($policy->create($user))->toBeFalse();
});
it('denies restore for service application', function () {
$user = Mockery::mock(User::class)->makePartial();
$serviceApp = Mockery::mock(ServiceApplication::class)->makePartial();
$policy = new ServiceApplicationPolicy;
expect($policy->restore($user, $serviceApp))->toBeFalse();
});
it('denies force delete for service application', function () {
$user = Mockery::mock(User::class)->makePartial();
$serviceApp = Mockery::mock(ServiceApplication::class)->makePartial();
$policy = new ServiceApplicationPolicy;
expect($policy->forceDelete($user, $serviceApp))->toBeFalse();
});

View file

@ -0,0 +1,37 @@
<?php
use App\Models\ServiceDatabase;
use App\Models\User;
use App\Policies\ServiceDatabasePolicy;
it('allows admin to create service database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ServiceDatabasePolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member from creating service database', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ServiceDatabasePolicy;
expect($policy->create($user))->toBeFalse();
});
it('denies restore for service database', function () {
$user = Mockery::mock(User::class)->makePartial();
$serviceDb = Mockery::mock(ServiceDatabase::class)->makePartial();
$policy = new ServiceDatabasePolicy;
expect($policy->restore($user, $serviceDb))->toBeFalse();
});
it('denies force delete for service database', function () {
$user = Mockery::mock(User::class)->makePartial();
$serviceDb = Mockery::mock(ServiceDatabase::class)->makePartial();
$policy = new ServiceDatabasePolicy;
expect($policy->forceDelete($user, $serviceDb))->toBeFalse();
});

View file

@ -0,0 +1,233 @@
<?php
use App\Models\Service;
use App\Models\User;
use App\Policies\ServicePolicy;
it('allows any user to view any services', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new ServicePolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their own team service', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->view($user, $service))->toBeTrue();
});
it('denies non-member to view another team service', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 2]);
$policy = new ServicePolicy;
expect($policy->view($user, $service))->toBeFalse();
});
it('denies view when service has no team', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn(null);
$policy = new ServicePolicy;
expect($policy->view($user, $service))->toBeFalse();
});
it('allows admin to create a service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new ServicePolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies member to create a service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new ServicePolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->update($user, $service))->toBeTrue();
});
it('denies team member to update their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->update($user, $service))->toBeFalse();
});
it('allows team admin to delete their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->delete($user, $service))->toBeTrue();
});
it('denies team member to delete their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->delete($user, $service))->toBeFalse();
});
it('denies delete when service has no team', function () {
$user = Mockery::mock(User::class)->makePartial();
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn(null);
$policy = new ServicePolicy;
expect($policy->delete($user, $service))->toBeFalse();
});
it('allows team admin to deploy their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->deploy($user, $service))->toBeTrue();
});
it('denies team member to deploy their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->deploy($user, $service))->toBeFalse();
});
it('allows team admin to stop their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->stop($user, $service))->toBeTrue();
});
it('denies team member to stop their own team service', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->stop($user, $service))->toBeFalse();
});
it('allows team admin to manage service environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->manageEnvironment($user, $service))->toBeTrue();
});
it('denies team member to manage service environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->manageEnvironment($user, $service))->toBeFalse();
});
it('allows team admin to access terminal', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->accessTerminal($user, $service))->toBeTrue();
});
it('denies team member to access terminal', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$service = Mockery::mock(Service::class)->makePartial();
$service->shouldReceive('team')->andReturn((object) ['id' => 1]);
$policy = new ServicePolicy;
expect($policy->accessTerminal($user, $service))->toBeFalse();
});
it('denies restore for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$service = Mockery::mock(Service::class)->makePartial();
$policy = new ServicePolicy;
expect($policy->restore($user, $service))->toBeFalse();
});
it('denies force delete for any user', function () {
$user = Mockery::mock(User::class)->makePartial();
$service = Mockery::mock(Service::class)->makePartial();
$policy = new ServicePolicy;
expect($policy->forceDelete($user, $service))->toBeFalse();
});

View file

@ -0,0 +1,144 @@
<?php
use App\Models\SharedEnvironmentVariable;
use App\Models\User;
use App\Policies\SharedEnvironmentVariablePolicy;
it('allows any user to view any shared environment variables', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their team shared environment variable', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->view($user, $model))->toBeTrue();
});
it('denies non-team member to view shared environment variable', function () {
$teams = collect([
(object) ['id' => 1, 'pivot' => (object) ['role' => 'member']],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 2;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->view($user, $model))->toBeFalse();
});
it('allows admin to create shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies non-admin to create shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->update($user, $model))->toBeTrue();
});
it('denies team member to update shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->update($user, $model))->toBeFalse();
});
it('allows team admin to delete shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->delete($user, $model))->toBeTrue();
});
it('denies team member to delete shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->delete($user, $model))->toBeFalse();
});
it('denies restore of shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->restore($user, $model))->toBeFalse();
});
it('denies force delete of shared environment variable', function () {
$user = Mockery::mock(User::class)->makePartial();
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->forceDelete($user, $model))->toBeFalse();
});
it('allows team admin to manage environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->manageEnvironment($user, $model))->toBeTrue();
});
it('denies team member to manage environment', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$model = Mockery::mock(SharedEnvironmentVariable::class)->makePartial();
$model->team_id = 1;
$policy = new SharedEnvironmentVariablePolicy;
expect($policy->manageEnvironment($user, $model))->toBeFalse();
});

View file

@ -0,0 +1,122 @@
<?php
use App\Models\StandaloneDocker;
use App\Models\User;
use App\Policies\StandaloneDockerPolicy;
it('allows any user to view any standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new StandaloneDockerPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their team standalone docker', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new StandaloneDockerPolicy;
expect($policy->view($user, $standaloneDocker))->toBeTrue();
});
it('denies user from viewing another team standalone docker', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 2]);
$policy = new StandaloneDockerPolicy;
expect($policy->view($user, $standaloneDocker))->toBeFalse();
});
it('allows admin to create standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new StandaloneDockerPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies non-admin from creating standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new StandaloneDockerPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new StandaloneDockerPolicy;
expect($policy->update($user, $standaloneDocker))->toBeTrue();
});
it('denies non-admin from updating standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new StandaloneDockerPolicy;
expect($policy->update($user, $standaloneDocker))->toBeFalse();
});
it('allows team admin to delete standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new StandaloneDockerPolicy;
expect($policy->delete($user, $standaloneDocker))->toBeTrue();
});
it('denies non-admin from deleting standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new StandaloneDockerPolicy;
expect($policy->delete($user, $standaloneDocker))->toBeFalse();
});
it('denies restore for standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new StandaloneDockerPolicy;
expect($policy->restore($user, $standaloneDocker))->toBeFalse();
});
it('denies force delete for standalone docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$standaloneDocker = Mockery::mock(StandaloneDocker::class)->makePartial();
$standaloneDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new StandaloneDockerPolicy;
expect($policy->forceDelete($user, $standaloneDocker))->toBeFalse();
});

View file

@ -0,0 +1,122 @@
<?php
use App\Models\SwarmDocker;
use App\Models\User;
use App\Policies\SwarmDockerPolicy;
it('allows any user to view any swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$policy = new SwarmDockerPolicy;
expect($policy->viewAny($user))->toBeTrue();
});
it('allows team member to view their team swarm docker', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new SwarmDockerPolicy;
expect($policy->view($user, $swarmDocker))->toBeTrue();
});
it('denies user from viewing another team swarm docker', function () {
$teams = collect([
(object) ['id' => 1],
]);
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 2]);
$policy = new SwarmDockerPolicy;
expect($policy->view($user, $swarmDocker))->toBeFalse();
});
it('allows admin to create swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(true);
$policy = new SwarmDockerPolicy;
expect($policy->create($user))->toBeTrue();
});
it('denies non-admin from creating swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdmin')->andReturn(false);
$policy = new SwarmDockerPolicy;
expect($policy->create($user))->toBeFalse();
});
it('allows team admin to update swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new SwarmDockerPolicy;
expect($policy->update($user, $swarmDocker))->toBeTrue();
});
it('denies non-admin from updating swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new SwarmDockerPolicy;
expect($policy->update($user, $swarmDocker))->toBeFalse();
});
it('allows team admin to delete swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(true);
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new SwarmDockerPolicy;
expect($policy->delete($user, $swarmDocker))->toBeTrue();
});
it('denies non-admin from deleting swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('isAdminOfTeam')->with(1)->andReturn(false);
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new SwarmDockerPolicy;
expect($policy->delete($user, $swarmDocker))->toBeFalse();
});
it('denies restore for swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new SwarmDockerPolicy;
expect($policy->restore($user, $swarmDocker))->toBeFalse();
});
it('denies force delete for swarm docker', function () {
$user = Mockery::mock(User::class)->makePartial();
$swarmDocker = Mockery::mock(SwarmDocker::class)->makePartial();
$swarmDocker->shouldReceive('getAttribute')->with('server')->andReturn((object) ['team_id' => 1]);
$policy = new SwarmDockerPolicy;
expect($policy->forceDelete($user, $swarmDocker))->toBeFalse();
});