Merge remote-tracking branch 'origin/next' into automation/sync-main-to-next

This commit is contained in:
Andras Bacsai
2026-10-26 12:40:59 +01:00
134 changed files with 4031 additions and 1325 deletions
@@ -1326,8 +1326,6 @@ class ApplicationsController extends Controller
return invalidTokenResponse();
}
$this->authorize('create', Application::class);
// removeUnnecessaryFieldsFromRequest() strips force_domain_override before the docker_compose_domains checks run.
$forceDomainOverride = $request->boolean('force_domain_override');
@@ -1479,6 +1477,7 @@ class ApplicationsController extends Controller
if (! $environment) {
return response()->json(['message' => 'Environment not found.'], 404);
}
$this->authorize('create', [Application::class, $environment]);
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
@@ -6153,6 +6152,7 @@ class ApplicationsController extends Controller
}
$this->authorize('update', $application);
$this->authorize('create', [Application::class, $application->environment]);
if ($request->boolean('clone_volumes') && $response = $this->deployAbilityForbiddenResponse($request, 'Cloning volume data stops and starts the source resource, so it needs a token with the deploy permission.')) {
return $response;
@@ -2093,9 +2093,6 @@ class DatabasesController extends Controller
return invalidTokenResponse();
}
// Use a generic authorization for database creation - using PostgreSQL as representative model
$this->authorize('create', StandalonePostgresql::class);
if ($request->boolean('instant_deploy')) {
abort_unless($request->user()->tokenCan('deploy') || $request->user()->tokenCan('root'), 403, 'Missing required permissions: deploy');
}
@@ -2140,6 +2137,8 @@ class DatabasesController extends Controller
if (! $environment) {
return response()->json(['message' => 'You need to provide a valid environment_name or environment_uuid.'], 422);
}
// StandalonePostgresql stands for every database type: they share one policy.
$this->authorize('create', [StandalonePostgresql::class, $environment]);
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
@@ -5238,6 +5237,7 @@ class DatabasesController extends Controller
}
$this->authorize('update', $database);
$this->authorize('create', [StandalonePostgresql::class, $database->environment]);
if ($request->boolean('clone_volumes') && $response = $this->deployAbilityForbiddenResponse($request, 'Cloning volume data stops and starts the source resource, so it needs a token with the deploy permission.')) {
return $response;
+12 -1
View File
@@ -13,6 +13,7 @@ use App\Models\ApplicationPreview;
use App\Models\Server;
use App\Models\Service;
use App\Models\Tag;
use App\Services\Access\AccessResolver;
use App\Support\ResourceStartActivity;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
@@ -74,7 +75,12 @@ class DeployController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$deployments_per_server = ApplicationDeploymentQueue::ownedByTeam((int) $teamId)->whereIn('status', ['in_progress', 'queued'])->get()->sortBy('id');
$query = ApplicationDeploymentQueue::ownedByTeam((int) $teamId)->whereIn('status', ['in_progress', 'queued']);
if (! app(AccessResolver::class)->coversWholeTeam($request->user(), $teamId)) {
// application_id is a string column, so compare with string IDs of the applications the actor can see.
$query->whereIn('application_id', Application::ownedByCurrentTeamAPI($teamId)->pluck('id')->map(fn ($id) => (string) $id));
}
$deployments_per_server = $query->get()->sortBy('id');
$deployments_per_server = $deployments_per_server->map(function ($deployment) {
return $this->removeSensitiveData($deployment);
});
@@ -232,6 +238,11 @@ class DeployController extends Controller
if (! $application || data_get($application->team(), 'id') !== (int) $teamId) {
return response()->json(['message' => 'You do not have permission to cancel this deployment.'], 403);
}
// For an actor limited by access grants, deployments of applications outside the grants do not exist.
// Soft-deleted applications stay included so a deploy that runs during a delete can still be cancelled.
if (! app(AccessResolver::class)->coversWholeTeam($request->user(), $teamId)) {
$this->authorize('deploy', $application);
}
// Check if deployment can be cancelled (must be queued or in_progress)
$cancellableStatuses = [
@@ -399,8 +399,6 @@ class ServicesController extends Controller
return invalidTokenResponse();
}
$this->authorize('create', Service::class);
if ($request->boolean('instant_deploy')) {
abort_unless($request->user()->tokenCan('deploy') || $request->user()->tokenCan('root'), 403, 'Missing required permissions: deploy');
}
@@ -483,6 +481,7 @@ class ServicesController extends Controller
if (! $environment) {
return response()->json(['message' => 'Environment not found.'], 404);
}
$this->authorize('create', [Service::class, $environment]);
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
@@ -692,6 +691,7 @@ class ServicesController extends Controller
if (! $environment) {
return response()->json(['message' => 'Environment not found.'], 404);
}
$this->authorize('create', [Service::class, $environment]);
$server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first();
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
@@ -3318,6 +3318,7 @@ class ServicesController extends Controller
}
$this->authorize('update', $service);
$this->authorize('create', [Service::class, $service->environment]);
if ($request->boolean('clone_volumes') && $response = $this->deployAbilityForbiddenResponse($request, 'Cloning volume data stops and starts the source resource, so it needs a token with the deploy permission.')) {
return $response;
@@ -165,7 +165,9 @@ class SharedEnvironmentVariablesController extends Controller
return $validated;
}
$this->authorize('create', SharedEnvironmentVariable::class);
if (in_array($type, ['team', 'server'], true)) {
$this->authorize('create', SharedEnvironmentVariable::class);
}
$scope = array_filter([
'project_id' => $attributes['project_id'] ?? null,
@@ -496,7 +498,7 @@ class SharedEnvironmentVariablesController extends Controller
return $project;
}
$this->authorize('view', $project);
$this->authorize('update', $project);
return $this->createEnv($request, $teamId, 'project', ['project_id' => $project->id]);
}
@@ -617,7 +619,7 @@ class SharedEnvironmentVariablesController extends Controller
return $environment;
}
$this->authorize('view', $project);
$this->authorize('view', $environment);
return $this->listEnvs($teamId, 'environment', ['environment_id' => $environment->id]);
}
@@ -658,7 +660,7 @@ class SharedEnvironmentVariablesController extends Controller
return $environment;
}
$this->authorize('view', $project);
$this->authorize('update', $environment);
return $this->createEnv($request, $teamId, 'environment', ['environment_id' => $environment->id]);
}
@@ -699,7 +701,7 @@ class SharedEnvironmentVariablesController extends Controller
return $environment;
}
$this->authorize('view', $project);
$this->authorize('view', $environment);
return $this->updateEnv(
$request,
@@ -745,7 +747,7 @@ class SharedEnvironmentVariablesController extends Controller
return $environment;
}
$this->authorize('view', $project);
$this->authorize('view', $environment);
return $this->deleteEnv(
$teamId,