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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -118,6 +118,7 @@ class TeamController extends Controller
if (is_null($team)) {
return response()->json(['message' => 'Team not found.'], 404);
}
$this->authorize('view', $team);
$team = $this->removeSensitiveData($team);
return response()->json(
@ -176,6 +177,7 @@ class TeamController extends Controller
if (is_null($team)) {
return response()->json(['message' => 'Team not found.'], 404);
}
$this->authorize('view', $team);
$members = $team->members;
$members->makeHidden([
'pivot',

View file

@ -65,58 +65,66 @@ class Heading extends Component
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)
{
$this->authorize('deploy', $this->application);
try {
$this->authorize('deploy', $this->application);
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.');
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.');
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()
@ -127,45 +135,53 @@ class Heading extends Component
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.');
StopApplication::dispatch($this->application, false, $this->docker_cleanup);
$this->dispatch('info', 'Gracefully stopping application.<br/>It could take a while depending on the application.');
StopApplication::dispatch($this->application, false, $this->docker_cleanup);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
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()) {
$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>');
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;
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()

View file

@ -215,24 +215,31 @@ class Previews extends Component
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)
{
$this->authorize('deploy', $this->application);
try {
$this->authorize('deploy', $this->application);
$this->add($pull_request_id, $pull_request_html_url);
$this->deploy($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);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false)
{
$this->authorize('deploy', $this->application);
try {
$this->authorize('deploy', $this->application);
$this->setDeploymentUuid();
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found && ! is_null($pull_request_html_url)) {
@ -291,9 +298,8 @@ class Previews extends Component
public function stop(int $pull_request_id)
{
$this->authorize('deploy', $this->application);
try {
$this->authorize('deploy', $this->application);
$server = $this->application->destination->server;
if ($this->application->destination->server->isSwarm()) {

View file

@ -14,9 +14,13 @@ class BackupNow extends Component
public function backupNow()
{
$this->authorize('manageBackups', $this->backup->database);
try {
$this->authorize('manageBackups', $this->backup->database);
DatabaseBackupJob::dispatch($this->backup);
$this->dispatch('success', 'Backup queued. It will be available in a few minutes.');
DatabaseBackupJob::dispatch($this->backup);
$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 @@ class Heading extends Component
public function restart()
{
$this->authorize('manage', $this->database);
try {
$this->authorize('manage', $this->database);
$activity = RestartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
$activity = RestartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function start()
{
$this->authorize('manage', $this->database);
try {
$this->authorize('manage', $this->database);
$activity = StartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
$activity = StartDatabase::run($this->database);
$this->dispatch('activityMonitor', $activity->id, ServiceStatusChanged::class);
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function render()

View file

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

View file

@ -30,18 +30,22 @@ class DeleteEnvironment extends Component
public function delete()
{
$this->validate([
'environment_id' => 'required|int',
]);
$environment = Environment::findOrFail($this->environment_id);
$this->authorize('delete', $environment);
try {
$this->validate([
'environment_id' => 'required|int',
]);
$environment = Environment::findOrFail($this->environment_id);
$this->authorize('delete', $environment);
if ($environment->isEmpty()) {
$environment->delete();
if ($environment->isEmpty()) {
$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 @@ class DeleteProject extends Component
public function delete()
{
$this->validate([
'project_id' => 'required|int',
]);
$project = Project::findOrFail($this->project_id);
$this->authorize('delete', $project);
try {
$this->validate([
'project_id' => 'required|int',
]);
$project = Project::findOrFail($this->project_id);
$this->authorize('delete', $project);
if ($project->isEmpty()) {
$project->delete();
if ($project->isEmpty()) {
$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\StartService;
use App\Actions\Service\StopService;
use App\Enums\ProcessStatus;
use App\Models\Service;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use Spatie\Activitylog\Models\Activity;
class Heading extends Component
{
use AuthorizesRequests;
public Service $service;
public array $parameters;
@ -99,13 +102,19 @@ class Heading extends Component
public function start()
{
$activity = StartService::run($this->service, pullLatestImages: true);
$this->dispatch('activityMonitor', $activity->id);
try {
$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()
{
try {
$this->authorize('deploy', $this->service);
$activities = Activity::where('properties->type_uuid', $this->service->uuid)
->where(function ($q) {
$q->where('properties->status', ProcessStatus::IN_PROGRESS->value)
@ -117,42 +126,53 @@ class Heading extends Component
}
$activity = StartService::run($this->service, pullLatestImages: true, stopBeforeStart: true);
$this->dispatch('activityMonitor', $activity->id);
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function stop()
{
try {
$this->authorize('stop', $this->service);
StopService::dispatch($this->service, false, $this->docker_cleanup);
} catch (\Exception $e) {
$this->dispatch('error', $e->getMessage());
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function restart()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
try {
$this->authorize('deploy', $this->service);
$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()
{
$this->checkDeployments();
if ($this->isDeploymentProgress) {
$this->dispatch('error', 'There is a deployment in progress.');
try {
$this->authorize('deploy', $this->service);
$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()

View file

@ -7,12 +7,15 @@ use App\Actions\Docker\GetContainersStatus;
use App\Events\ApplicationStatusChanged;
use App\Models\Server;
use App\Models\StandaloneDocker;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Collection;
use Livewire\Component;
use Visus\Cuid2\Cuid2;
class Destination extends Component
{
use AuthorizesRequests;
public $resource;
public Collection $networks;
@ -59,6 +62,7 @@ class Destination extends Component
public function stop($serverId)
{
try {
$this->authorize('deploy', $this->resource);
$server = Server::ownedByCurrentTeam()->findOrFail($serverId);
StopApplicationOneServer::run($this->resource, $server);
$this->refreshServers();
@ -70,6 +74,7 @@ class Destination extends Component
public function redeploy(int $network_id, int $server_id)
{
try {
$this->authorize('deploy', $this->resource);
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>');
@ -110,15 +115,20 @@ class Destination extends Component
public function promote(int $network_id, int $server_id)
{
$main_destination = $this->resource->destination;
$this->resource->update([
'destination_id' => $network_id,
'destination_type' => StandaloneDocker::class,
]);
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
$this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]);
$this->refreshServers();
$this->resource->refresh();
try {
$this->authorize('update', $this->resource);
$main_destination = $this->resource->destination;
$this->resource->update([
'destination_id' => $network_id,
'destination_type' => StandaloneDocker::class,
]);
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
$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()
@ -130,13 +140,19 @@ class Destination extends Component
public function addServer(int $network_id, int $server_id)
{
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]);
$this->dispatch('refresh');
try {
$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)
{
try {
$this->authorize('update', $this->resource);
if (! verifyPasswordConfirmation($password, $this)) {
return;
}

View file

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

View file

@ -47,224 +47,87 @@ class ResourceOperations extends Component
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);
$new_destination = StandaloneDocker::whereHas('server', $teamScope)->find($destination_id);
if (! $new_destination) {
$new_destination = SwarmDocker::whereHas('server', $teamScope)->find($destination_id);
}
if (! $new_destination) {
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
) {
$teamScope = fn ($q) => $q->where('team_id', currentTeam()->id);
$new_destination = StandaloneDocker::whereHas('server', $teamScope)->find($destination_id);
if (! $new_destination) {
$new_destination = SwarmDocker::whereHas('server', $teamScope)->find($destination_id);
}
if (! $new_destination) {
return $this->addError('destination_id', 'Destination not found.');
}
$uuid = (string) new Cuid2;
$new_resource = $this->resource->replicate([
'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();
$server = $new_destination->server;
$tags = $this->resource->tags;
foreach ($tags as $tag) {
$new_resource->tags()->attach($tag->id);
}
if ($this->resource->getMorphClass() === \App\Models\Application::class) {
$new_resource = clone_application($this->resource, $new_destination, ['uuid' => $uuid], $this->cloneVolumeData);
$new_resource->persistentStorages()->delete();
$persistentVolumes = $this->resource->persistentStorages()->get();
foreach ($persistentVolumes as $volume) {
$originalName = $volume->name;
$newName = '';
$route = route('project.application.configuration', [
'project_uuid' => $this->projectUuid,
'environment_uuid' => $this->environmentUuid,
'application_uuid' => $new_resource->uuid,
]).'#resource-operations';
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 {
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) {
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;
$newBackup = $backup->replicate([
$new_resource = $this->resource->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'uuid' => $uuid,
'database_id' => $new_resource->id,
'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([
'name' => $this->resource->name.'-clone-'.$uuid,
'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) {
$originalName = $volume->name;
$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 {
$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([
@ -273,79 +136,220 @@ class ResourceOperations extends Component
'updated_at',
])->fill([
'name' => $newName,
'resource_id' => $application->id,
'resource_id' => $new_resource->id,
]);
$newPersistentVolume->save();
if ($this->cloneVolumeData) {
try {
StopService::dispatch($application);
StopDatabase::dispatch($this->resource);
$sourceVolume = $volume->name;
$targetVolume = $newPersistentVolume->name;
$sourceServer = $application->service->destination->server;
$sourceServer = $this->resource->destination->server;
$targetServer = $new_resource->destination->server;
VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume);
StartService::dispatch($application);
StartDatabase::dispatch($this->resource);
} 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([
$fileStorages = $this->resource->fileStorages()->get();
foreach ($fileStorages as $storage) {
$newStorage = $storage->replicate([
'id',
'created_at',
'updated_at',
])->fill([
'name' => $newName,
'resource_id' => $database->id,
'resource_id' => $new_resource->id,
]);
$newPersistentVolume->save();
$newStorage->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;
$scheduledBackups = $this->resource->scheduledBackups()->get();
foreach ($scheduledBackups as $backup) {
$uuid = (string) new Cuid2;
$newBackup = $backup->replicate([
'id',
'created_at',
'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);
} catch (\Exception $e) {
\Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage());
$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',
]);
$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);
}
$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) {
return handleError($e, $this);
}
}

View file

@ -23,6 +23,8 @@ class ApiTokens extends Component
public bool $canUseWritePermissions = false;
public bool $canUseDeployPermissions = false;
public function render()
{
return view('livewire.security.api-tokens');
@ -33,6 +35,7 @@ class ApiTokens extends Component
$this->isApiEnabled = InstanceSettings::get()->is_api_enabled;
$this->canUseRootPermissions = auth()->user()->can('useRootPermissions', PersonalAccessToken::class);
$this->canUseWritePermissions = auth()->user()->can('useWritePermissions', PersonalAccessToken::class);
$this->canUseDeployPermissions = auth()->user()->can('useDeployPermissions', PersonalAccessToken::class);
$this->getTokens();
}
@ -60,6 +63,13 @@ class ApiTokens extends Component
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') {
$this->permissions = ['root'];
} elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) {
@ -88,6 +98,10 @@ class ApiTokens extends Component
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([
'description' => 'required|min:3|max:255',
]);

View file

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

View file

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

View file

@ -21,8 +21,12 @@ class Index extends Component
public function cleanupUnusedKeys()
{
$this->authorize('create', PrivateKey::class);
PrivateKey::cleanupUnusedKeys();
$this->dispatch('success', 'Unused keys have been cleaned up.');
try {
$this->authorize('create', PrivateKey::class);
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()
{
$this->authorize('viewAny', CloudProviderToken::class);
$this->loadTokens();
$this->loadSavedCloudInitScripts();
$this->server_name = generate_random_name();
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get();
try {
$this->authorize('viewAny', CloudProviderToken::class);
$this->loadTokens();
$this->loadSavedCloudInitScripts();
$this->server_name = generate_random_name();
$this->private_keys = PrivateKey::ownedAndOnlySShKeys()->where('id', '!=', 0)->get();
if ($this->private_keys->count() > 0) {
$this->private_key_id = $this->private_keys->first()->id;
if ($this->private_keys->count() > 0) {
$this->private_key_id = $this->private_keys->first()->id;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}

View file

@ -96,11 +96,15 @@ class Proxy extends Component
public function changeProxy()
{
$this->authorize('update', $this->server);
$this->server->proxy = null;
$this->server->save();
try {
$this->authorize('update', $this->server);
$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)

View file

@ -22,34 +22,38 @@ class DynamicConfigurationNavbar extends Component
public function delete(string $fileName)
{
$this->authorize('update', $this->server);
$proxy_path = $this->server->proxyPath();
$proxy_type = $this->server->proxyType();
try {
$this->authorize('update', $this->server);
$proxy_path = $this->server->proxyPath();
$proxy_type = $this->server->proxyType();
// Decode filename: pipes are used to encode dots for Livewire property binding
// (e.g., 'my|service.yaml' -> 'my.service.yaml')
// This must happen BEFORE validation because validateShellSafePath() correctly
// rejects pipe characters as dangerous shell metacharacters
$file = str_replace('|', '.', $fileName);
// Decode filename: pipes are used to encode dots for Livewire property binding
// (e.g., 'my|service.yaml' -> 'my.service.yaml')
// This must happen BEFORE validation because validateShellSafePath() correctly
// rejects pipe characters as dangerous shell metacharacters
$file = str_replace('|', '.', $fileName);
// Validate filename to prevent command injection
validateShellSafePath($file, 'proxy configuration filename');
// Validate filename to prevent command injection
validateShellSafePath($file, 'proxy configuration filename');
if ($proxy_type === 'CADDY' && $file === 'Caddyfile') {
$this->dispatch('error', 'Cannot delete Caddyfile.');
if ($proxy_type === 'CADDY' && $file === '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()

View file

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

View file

@ -41,7 +41,11 @@ class Patches extends Component
{
$this->parameters = get_route_parameters();
$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()
@ -69,14 +73,14 @@ class Patches extends Component
public function updateAllPackages()
{
$this->authorize('update', $this->server);
if (! $this->packageManager || ! $this->osId) {
$this->dispatch('error', message: 'Run "Check for updates" first.');
return;
}
try {
$this->authorize('update', $this->server);
if (! $this->packageManager || ! $this->osId) {
$this->dispatch('error', message: 'Run "Check for updates" first.');
return;
}
$activity = UpdatePackage::run(
server: $this->server,
packageManager: $this->packageManager,
@ -84,8 +88,8 @@ class Patches extends Component
all: true
);
$this->dispatch('activityMonitor', $activity->id, ServerPackageUpdated::class);
} catch (\Exception $e) {
$this->dispatch('error', message: $e->getMessage());
} catch (\Throwable $e) {
return handleError($e, $this);
}
}

View file

@ -286,18 +286,22 @@ class Show extends Component
public function checkLocalhostConnection()
{
$this->syncData(true);
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = $this->isReachable = true;
$this->server->settings->is_usable = $this->isUsable = true;
$this->server->settings->save();
ServerReachabilityChanged::dispatch($this->server);
} 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);
try {
$this->syncData(true);
['uptime' => $uptime, 'error' => $error] = $this->server->validateConnection();
if ($uptime) {
$this->dispatch('success', 'Server is reachable.');
$this->server->settings->is_reachable = $this->isReachable = true;
$this->server->settings->is_usable = $this->isUsable = true;
$this->server->settings->save();
ServerReachabilityChanged::dispatch($this->server);
} 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 @@ class ValidateAndInstall extends Component
public function retry()
{
$this->authorize('update', $this->server);
$this->uptime = null;
$this->supported_os_type = null;
$this->prerequisites_installed = null;
$this->docker_installed = null;
$this->docker_compose_installed = null;
$this->docker_version = null;
$this->error = null;
$this->number_of_tries = 0;
$this->init();
try {
$this->authorize('update', $this->server);
$this->uptime = null;
$this->supported_os_type = null;
$this->prerequisites_installed = null;
$this->docker_installed = null;
$this->docker_compose_installed = null;
$this->docker_version = null;
$this->error = null;
$this->number_of_tries = 0;
$this->init();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function validateConnection()
{
$this->authorize('update', $this->server);
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) {
$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>';
$this->server->update([
'validation_logs' => $this->error,
]);
try {
$this->authorize('update', $this->server);
['uptime' => $this->uptime, 'error' => $error] = $this->server->validateConnection();
if (! $this->uptime) {
$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>';
$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()

View file

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

View file

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

View file

@ -18,7 +18,11 @@ class Show extends Component
if (! $this->storage) {
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()

View file

@ -95,23 +95,27 @@ class Index extends Component
public function delete()
{
$currentTeam = currentTeam();
$this->authorize('delete', $currentTeam);
$currentTeam->delete();
try {
$currentTeam = currentTeam();
$this->authorize('delete', $currentTeam);
$currentTeam->delete();
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === Auth::id()) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
DB::table('sessions')->where('id', $session->id)->delete();
}
});
$currentTeam->members->each(function ($user) use ($currentTeam) {
if ($user->id === Auth::id()) {
return;
}
$user->teams()->detach($currentTeam);
$session = DB::table('sessions')->where('user_id', $user->id)->first();
if ($session) {
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 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
$team->webhookNotificationSettings()->create();
});
static::saving(function ($team) {
static::updating(function ($team) {
if (auth()->user()?->isMember()) {
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
{
// Authorization temporarily disabled
/*
// Users can view their own API tokens
return true;
*/
return true;
}
@ -25,12 +20,7 @@ class ApiTokenPolicy
*/
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 true;
}
/**
@ -38,11 +28,6 @@ class ApiTokenPolicy
*/
public function create(User $user): bool
{
// Authorization temporarily disabled
/*
// All authenticated users can create their own API tokens
return true;
*/
return true;
}
@ -51,12 +36,7 @@ class ApiTokenPolicy
*/
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 true;
}
/**
@ -64,12 +44,7 @@ class ApiTokenPolicy
*/
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 true;
}
/**
@ -77,11 +52,6 @@ class ApiTokenPolicy
*/
public function manage(User $user): bool
{
// Authorization temporarily disabled
/*
// All authenticated users can manage their own API tokens
return true;
*/
return true;
}
@ -90,7 +60,6 @@ class ApiTokenPolicy
*/
public function useRootPermissions(User $user): bool
{
// Only admins and owners can use root permissions
return $user->isAdmin() || $user->isOwner();
}
@ -99,11 +68,14 @@ class ApiTokenPolicy
*/
public function useWritePermissions(User $user): bool
{
// Authorization temporarily disabled
/*
// Only admins and owners can use write permissions
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
{
// Authorization temporarily disabled
/*
return true;
*/
return true;
}
@ -25,11 +21,9 @@ class ApplicationPolicy
*/
public function view(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return true;
*/
return true;
$teamId = $this->getTeamId($application);
return $teamId !== null && $user->teams->contains('id', $teamId);
}
/**
@ -37,15 +31,7 @@ class ApplicationPolicy
*/
public function create(User $user): bool
{
// Authorization temporarily disabled
/*
if ($user->isAdmin()) {
return true;
}
return false;
*/
return true;
return $user->isAdmin();
}
/**
@ -53,15 +39,17 @@ class ApplicationPolicy
*/
public function update(User $user, Application $application): Response
{
// Authorization temporarily disabled
/*
if ($user->isAdmin()) {
$teamId = $this->getTeamId($application);
if ($teamId === null) {
return Response::deny('Application team not found.');
}
if ($user->isAdminOfTeam($teamId)) {
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::allow();
return Response::deny('You need at least admin or owner permissions to update this application.');
}
/**
@ -69,15 +57,9 @@ class ApplicationPolicy
*/
public function delete(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
if ($user->isAdmin()) {
return true;
}
$teamId = $this->getTeamId($application);
return false;
*/
return true;
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -85,11 +67,7 @@ class ApplicationPolicy
*/
public function restore(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return true;
*/
return true;
return false;
}
/**
@ -97,11 +75,7 @@ class ApplicationPolicy
*/
public function forceDelete(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
return false;
}
/**
@ -109,11 +83,9 @@ class ApplicationPolicy
*/
public function deploy(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
$teamId = $this->getTeamId($application);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -121,11 +93,9 @@ class ApplicationPolicy
*/
public function manageDeployments(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
$teamId = $this->getTeamId($application);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -133,11 +103,9 @@ class ApplicationPolicy
*/
public function manageEnvironment(User $user, Application $application): bool
{
// Authorization temporarily disabled
/*
return $user->isAdmin() && $user->teams->contains('id', $application->team()->first()->id);
*/
return true;
$teamId = $this->getTeamId($application);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -145,10 +113,11 @@ class ApplicationPolicy
*/
public function cleanupDeploymentQueue(User $user): bool
{
// Authorization temporarily disabled
/*
return $user->isAdmin();
*/
return true;
}
private function getTeamId(Application $application): ?int
{
return $application->team()?->id;
}
}

View file

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

View file

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

View file

@ -20,8 +20,9 @@ class DatabasePolicy
*/
public function view(User $user, $database): bool
{
// return $user->teams->contains('id', $database->team()->first()->id);
return true;
$teamId = $this->getTeamId($database);
return $teamId !== null && $user->teams->contains('id', $teamId);
}
/**
@ -29,21 +30,25 @@ class DatabasePolicy
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
return $user->isAdmin();
}
/**
* 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)) {
// return Response::allow();
// }
$teamId = $this->getTeamId($database);
// return Response::deny('As a member, you cannot update this database.<br/><br/>You need at least admin or owner permissions.');
return true;
if ($teamId === null) {
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 @@ class DatabasePolicy
*/
public function delete(User $user, $database): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
$teamId = $this->getTeamId($database);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -60,8 +66,7 @@ class DatabasePolicy
*/
public function restore(User $user, $database): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
return false;
}
/**
@ -69,8 +74,7 @@ class DatabasePolicy
*/
public function forceDelete(User $user, $database): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
return false;
}
/**
@ -78,8 +82,9 @@ class DatabasePolicy
*/
public function manage(User $user, $database): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
$teamId = $this->getTeamId($database);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -87,8 +92,9 @@ class DatabasePolicy
*/
public function manageBackups(User $user, $database): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
$teamId = $this->getTeamId($database);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -96,7 +102,17 @@ class DatabasePolicy
*/
public function manageEnvironment(User $user, $database): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $database->team()->first()->id);
return true;
$teamId = $this->getTeamId($database);
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 @@ class EnvironmentPolicy
*/
public function view(User $user, Environment $environment): bool
{
// return $user->teams->contains('id', $environment->project->team_id);
return true;
$teamId = $this->getTeamId($environment);
return $teamId !== null && $user->teams->contains('id', $teamId);
}
/**
@ -29,8 +30,7 @@ class EnvironmentPolicy
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
return $user->isAdmin();
}
/**
@ -38,8 +38,9 @@ class EnvironmentPolicy
*/
public function update(User $user, Environment $environment): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
$teamId = $this->getTeamId($environment);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -47,8 +48,9 @@ class EnvironmentPolicy
*/
public function delete(User $user, Environment $environment): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
$teamId = $this->getTeamId($environment);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -56,8 +58,7 @@ class EnvironmentPolicy
*/
public function restore(User $user, Environment $environment): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
return false;
}
/**
@ -65,7 +66,11 @@ class EnvironmentPolicy
*/
public function forceDelete(User $user, Environment $environment): bool
{
// return $user->isAdmin() && $user->teams->contains('id', $environment->project->team_id);
return true;
return false;
}
private function getTeamId(Environment $environment): ?int
{
return $environment->project?->team_id;
}
}

View file

@ -20,7 +20,9 @@ class EnvironmentVariablePolicy
*/
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 @@ class EnvironmentVariablePolicy
*/
public function create(User $user): bool
{
return true;
return $user->isAdmin();
}
/**
@ -36,7 +38,9 @@ class EnvironmentVariablePolicy
*/
public function update(User $user, EnvironmentVariable $environmentVariable): bool
{
return true;
$teamId = $this->getTeamId($environmentVariable);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -44,7 +48,9 @@ class EnvironmentVariablePolicy
*/
public function delete(User $user, EnvironmentVariable $environmentVariable): bool
{
return true;
$teamId = $this->getTeamId($environmentVariable);
return $teamId !== null && $user->isAdminOfTeam($teamId);
}
/**
@ -52,7 +58,7 @@ class EnvironmentVariablePolicy
*/
public function restore(User $user, EnvironmentVariable $environmentVariable): bool
{
return true;
return false;
}
/**
@ -60,7 +66,7 @@ class EnvironmentVariablePolicy
*/
public function forceDelete(User $user, EnvironmentVariable $environmentVariable): bool
{
return true;
return false;
}
/**
@ -68,6 +74,19 @@ class EnvironmentVariablePolicy
*/
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 @@ class GithubAppPolicy
*/
public function view(User $user, GithubApp $githubApp): bool
{
// return $user->teams->contains('id', $githubApp->team_id) || $githubApp->is_system_wide;
return true;
if ($githubApp->is_system_wide) {
return true;
}
return $user->teams->contains('id', $githubApp->team_id);
}
/**
@ -29,8 +32,7 @@ class GithubAppPolicy
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
return $user->isAdmin();
}
/**
@ -39,12 +41,10 @@ class GithubAppPolicy
public function update(User $user, GithubApp $githubApp): bool
{
if ($githubApp->is_system_wide) {
// return $user->isAdmin();
return true;
return $user->canAccessSystemResources();
}
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id);
return true;
return $user->isAdminOfTeam($githubApp->team_id);
}
/**
@ -53,12 +53,10 @@ class GithubAppPolicy
public function delete(User $user, GithubApp $githubApp): bool
{
if ($githubApp->is_system_wide) {
// return $user->isAdmin();
return true;
return $user->canAccessSystemResources();
}
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id);
return true;
return $user->isAdminOfTeam($githubApp->team_id);
}
/**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -28,8 +28,7 @@ class StandaloneDockerPolicy
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
return $user->isAdmin();
}
/**
@ -37,7 +36,7 @@ class StandaloneDockerPolicy
*/
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 @@ class StandaloneDockerPolicy
*/
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 @@ class SwarmDockerPolicy
*/
public function create(User $user): bool
{
// return $user->isAdmin();
return true;
return $user->isAdmin();
}
/**
@ -37,7 +36,7 @@ class SwarmDockerPolicy
*/
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 @@ class SwarmDockerPolicy
*/
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
</x-slot>
@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"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -18,7 +18,7 @@
cache)
</div>
@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"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />

View file

@ -2,7 +2,7 @@
<h1>Notifications</h1>
<div class="subtitle">Get notified about your infrastructure.</div>
<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() }}
href="{{ route('notifications.email') }}">
<button>Email</button>

View file

@ -3,7 +3,7 @@
Advanced
</x-slot>
@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"
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
@ -17,7 +17,7 @@
Pull Latest Images & Restart
</div>
@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"
stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke: currentColor;" class="w-6 h-6" stroke-width="2">
@ -27,7 +27,7 @@
Force Restart
</div>
@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"
stroke-linecap="round" stroke-linejoin="round" data-darkreader-inline-stroke=""
style="--darkreader-inline-stroke: currentColor;" class="w-4 h-4" stroke-width="2">
@ -36,7 +36,7 @@
</svg>
Force Deploy
</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">
<path fill="currentColor" d="M26 20h-6v-2h6zm4 8h-6v-2h6zm-2-4h-6v-2h6z" />
<path fill="currentColor"

View file

@ -36,88 +36,88 @@
<div>Please load a Compose file.</div>
@else
@if (!$application->destination->server->isSwarm())
<div>
<x-applications.advanced :application="$application" />
</div>
<div>
<x-applications.advanced :application="$application" />
</div>
@endif
<div class="flex flex-wrap gap-2">
@if (!str($application->status)->startsWith('exited'))
@if (!$application->destination->server->isSwarm())
<x-forms.button 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"
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="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 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>
<div class="flex flex-wrap gap-2">
@if (!str($application->status)->startsWith('exited'))
@if (!$application->destination->server->isSwarm())
<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"
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="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 d="M7.05 11.038v-3.988"></path>
</svg>
Update Service
</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
Redeploy
</x-forms.button>
@endif
@endif
<x-modal-confirmation 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>
@if ($application->build_pack !== 'dockercompose')
@if ($application->destination->server->isSwarm())
<x-forms.button canGate="deploy" :canResource="$application" 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>
Update Service
</x-forms.button>
@else
<x-forms.button canGate="deploy" :canResource="$application" 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>
@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>
Stop
</x-slot:button-title>
</x-modal-confirmation>
@else
<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>
Deploy
</x-forms.button>
@endif
</div>
@endif
</div>
</div>

View file

@ -38,72 +38,72 @@
@endif
</nav>
@if ($database->destination->server->isFunctional())
<div class="flex flex-wrap gap-2 items-center">
@if (!str($database->status)->startsWith('exited'))
<x-modal-confirmation title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
:actions="[
'This database will be unavailable during the restart.',
'If the database is currently in use data could be lost.',
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
:dispatchEvent="true" dispatchEventType="restartEvent">
<x-slot:button-title>
<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-slot:button-title>
</x-modal-confirmation>
<x-modal-confirmation title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[
'This database will be stopped.',
'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).',
]" :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"
<div class="flex flex-wrap gap-2 items-center">
@if (!str($database->status)->startsWith('exited'))
<x-modal-confirmation :disabled="!auth()->user()->can('manage', $database)" title="Confirm Database Restart?" buttonTitle="Restart" submitAction="restart"
:actions="[
'This database will be unavailable during the restart.',
'If the database is currently in use data could be lost.',
]" :confirmWithText="false" :confirmWithPassword="false" step2ButtonText="Restart Database"
:dispatchEvent="true" dispatchEventType="restartEvent">
<x-slot:button-title>
<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-slot:button-title>
</x-modal-confirmation>
<x-modal-confirmation :disabled="!auth()->user()->can('manage', $database)" title="Confirm Database Stopping?" buttonTitle="Stop" submitAction="stop"
:checkboxes="$checkboxes" :actions="[
'This database will be stopped.',
'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).',
]" :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
<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">
<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>
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Stop
</x-slot:button-title>
</x-modal-confirmation>
@else
<button @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">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path d="M7 4v16l13 -8z" />
</svg>
Start
</button>
@endif
@script
<script>
$wire.$on('startEvent', () => {
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>
Start
</button>
@endif
@script
<script>
$wire.$on('startEvent', () => {
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
<div class="text-error">Underlying server is not functional.</div>
@endif

View file

@ -30,7 +30,7 @@
<div class="flex flex-wrap order-first gap-2 items-center sm:order-last">
<x-services.advanced :service="$service" />
@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">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
@ -40,7 +40,7 @@
</svg>
Restart
</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')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
@ -58,7 +58,7 @@
</x-slot:button-title>
</x-modal-confirmation>
@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">
<g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2">
@ -68,7 +68,7 @@
</svg>
Restart
</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')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
@ -86,7 +86,7 @@
</x-slot:button-title>
</x-modal-confirmation>
@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"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">
@ -96,7 +96,7 @@
Deploy
</button>
@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')]"
:confirmWithText="false" :confirmWithPassword="false" step1ButtonText="Continue" step2ButtonText="Confirm">
<x-slot:button-title>
@ -113,7 +113,7 @@
Stop
</x-slot:button-title>
</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"
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
stroke-linejoin="round">

View file

@ -3,12 +3,12 @@
<h2>Healthchecks</h2>
<x-forms.button canGate="update" :canResource="$resource" type="submit">Save</x-forms.button>
@if (!$healthCheckEnabled)
<x-modal-confirmation title="Confirm Healthcheck Enable?" buttonTitle="Enable Healthcheck"
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!"
step2ButtonText="Enable Healthcheck" :confirmWithText="false" :confirmWithPassword="false"
isHighlightedButton>
</x-modal-confirmation>
<x-modal-confirmation :disabled="!auth()->user()->can('update', $resource)" title="Confirm Healthcheck Enable?" buttonTitle="Enable Healthcheck"
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!"
step2ButtonText="Enable Healthcheck" :confirmWithText="false" :confirmWithPassword="false"
isHighlightedButton>
</x-modal-confirmation>
@else
<x-forms.button canGate="update" :canResource="$resource" wire:click="toggleHealthcheck">Disable Healthcheck</x-forms.button>
@endif

View file

@ -50,8 +50,13 @@
helper="Write access requires admin or owner role" :checked="false"></x-forms.checkbox>
@endif
<x-forms.checkbox label="deploy" wire:model.live="permissions" domValue="deploy"
helper="Can trigger deploy webhooks." :checked="in_array('deploy', $permissions)"></x-forms.checkbox>
@if ($canUseDeployPermissions)
<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"
:checked="in_array('read', $permissions)"></x-forms.checkbox>
<x-forms.checkbox label="read:sensitive" wire:model.live="permissions" domValue="read:sensitive"

View file

@ -62,7 +62,7 @@
<div class="subtitle">{{ data_get($server, 'name') }}</div>
<div class="navbar-main">
<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', [
'server_uuid' => data_get($server, 'uuid'),
]) }}" {{ wireNavigate() }}>

View file

@ -134,22 +134,22 @@
{{ data_get($resource, 'State') }}
</td>
<td class="flex gap-2 px-5 py-4 text-sm whitespace-nowrap">
@if (data_get($resource, 'State') === 'running')
<x-forms.button
wire:click="restartUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Restart</x-forms.button>
<x-forms.button isError
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@elseif (data_get($resource, 'State') === 'exited')
<x-forms.button
wire:click="startUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Start</x-forms.button>
@elseif (data_get($resource, 'State') === 'restarting')
<x-forms.button
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@endif
@if (data_get($resource, 'State') === 'running')
<x-forms.button canGate="update" :canResource="$server"
wire:click="restartUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Restart</x-forms.button>
<x-forms.button canGate="update" :canResource="$server" isError
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@elseif (data_get($resource, 'State') === 'exited')
<x-forms.button canGate="update" :canResource="$server"
wire:click="startUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Start</x-forms.button>
@elseif (data_get($resource, 'State') === 'restarting')
<x-forms.button canGate="update" :canResource="$server"
wire:click="stopUnmanaged('{{ data_get($resource, 'ID') }}')"
wire:key="{{ data_get($resource, 'ID') }}">Stop</x-forms.button>
@endif
</td>
</tr>
@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('manageInvitations permission (privilege escalation fix)', function ()
});
});
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 () {
test('owner can view team', function () {
$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
use App\Models\PrivateKey;
use App\Models\User;
use App\Policies\PrivateKeyPolicy;
@ -11,10 +12,8 @@ it('allows root team admin to view system private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeTrue();
@ -28,10 +27,8 @@ it('allows root team owner to view system private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeTrue();
@ -45,10 +42,8 @@ it('denies regular member of root team to view system private key', function ()
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeFalse();
@ -62,10 +57,8 @@ it('denies non-root team member to view system private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeFalse();
@ -79,10 +72,8 @@ it('allows team member to view their own team private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 1;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 1;
$policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeTrue();
@ -96,10 +87,8 @@ it('denies team member to view another team private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 2;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 2;
$policy = new PrivateKeyPolicy;
expect($policy->view($user, $privateKey))->toBeFalse();
@ -113,10 +102,8 @@ it('allows root team admin to update system private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeTrue();
@ -130,10 +117,8 @@ it('denies root team member to update system private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeFalse();
@ -147,10 +132,8 @@ it('allows team admin to update their own team private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 1;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 1;
$policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeTrue();
@ -164,10 +147,8 @@ it('denies team member to update their own team private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 1;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 1;
$policy = new PrivateKeyPolicy;
expect($policy->update($user, $privateKey))->toBeFalse();
@ -181,10 +162,8 @@ it('allows root team admin to delete system private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
expect($policy->delete($user, $privateKey))->toBeTrue();
@ -198,10 +177,8 @@ it('denies root team member to delete system private key', function () {
$user = Mockery::mock(User::class)->makePartial();
$user->shouldReceive('getAttribute')->with('teams')->andReturn($teams);
$privateKey = new class
{
public $team_id = 0;
};
$privateKey = Mockery::mock(PrivateKey::class)->makePartial();
$privateKey->team_id = 0;
$policy = new PrivateKeyPolicy;
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();
});