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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -59,7 +59,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
$team->webhookNotificationSettings()->create(); $team->webhookNotificationSettings()->create();
}); });
static::saving(function ($team) { static::updating(function ($team) {
if (auth()->user()?->isMember()) { if (auth()->user()?->isMember()) {
throw new \Exception('You are not allowed to update this team.'); throw new \Exception('You are not allowed to update this team.');
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,9 @@ class EnvironmentVariablePolicy
*/ */
public function view(User $user, EnvironmentVariable $environmentVariable): bool public function view(User $user, EnvironmentVariable $environmentVariable): bool
{ {
return true; $teamId = $this->getTeamId($environmentVariable);
return $teamId !== null && $user->teams->contains('id', $teamId);
} }
/** /**
@ -28,7 +30,7 @@ class EnvironmentVariablePolicy
*/ */
public function create(User $user): bool 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 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 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 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 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 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 public function view(User $user, GithubApp $githubApp): bool
{ {
// return $user->teams->contains('id', $githubApp->team_id) || $githubApp->is_system_wide; if ($githubApp->is_system_wide) {
return true; return true;
}
return $user->teams->contains('id', $githubApp->team_id);
} }
/** /**
@ -29,8 +32,7 @@ class GithubAppPolicy
*/ */
public function create(User $user): bool public function create(User $user): bool
{ {
// return $user->isAdmin(); return $user->isAdmin();
return true;
} }
/** /**
@ -39,12 +41,10 @@ class GithubAppPolicy
public function update(User $user, GithubApp $githubApp): bool public function update(User $user, GithubApp $githubApp): bool
{ {
if ($githubApp->is_system_wide) { if ($githubApp->is_system_wide) {
// return $user->isAdmin(); return $user->canAccessSystemResources();
return true;
} }
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id); return $user->isAdminOfTeam($githubApp->team_id);
return true;
} }
/** /**
@ -53,12 +53,10 @@ class GithubAppPolicy
public function delete(User $user, GithubApp $githubApp): bool public function delete(User $user, GithubApp $githubApp): bool
{ {
if ($githubApp->is_system_wide) { if ($githubApp->is_system_wide) {
// return $user->isAdmin(); return $user->canAccessSystemResources();
return true;
} }
// return $user->isAdmin() && $user->teams->contains('id', $githubApp->team_id); return $user->isAdminOfTeam($githubApp->team_id);
return true;
} }
/** /**

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

11
tasks/lessons.md Normal file
View file

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

View file

@ -156,6 +156,54 @@ describe('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 () { describe('view permission', function () {
test('owner can view team', function () { test('owner can view team', function () {
$this->actingAs($this->owner); $this->actingAs($this->owner);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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