Merge remote-tracking branch 'origin/next' into api-sensitive-data-scrubber

This commit is contained in:
Andras Bacsai
2026-06-15 13:29:25 +02:00
339 changed files with 11186 additions and 2608 deletions
@@ -30,7 +30,6 @@ use Illuminate\Validation\Rule;
use OpenApi\Attributes as OA;
use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2;
class ApplicationsController extends Controller
{
@@ -64,6 +63,10 @@ class ApplicationsController extends Controller
$this->hideNestedServerSecrets($application);
}
if ($application->is_shown_once ?? false) {
$application->makeHidden(['value', 'real_value']);
}
return serializeApiResponse($application);
}
@@ -1251,7 +1254,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -1490,7 +1493,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -1699,7 +1702,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -1745,7 +1748,7 @@ class ApplicationsController extends Controller
], 422);
}
if (! $request->has('name')) {
$request->offsetSet('name', 'dockerfile-'.new Cuid2);
$request->offsetSet('name', 'dockerfile-'.new_public_id());
}
$return = $this->validateDataApplications($request, $server);
@@ -1819,7 +1822,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -1863,7 +1866,7 @@ class ApplicationsController extends Controller
], 422);
}
if (! $request->has('name')) {
$request->offsetSet('name', 'docker-image-'.new Cuid2);
$request->offsetSet('name', 'docker-image-'.new_public_id());
}
$return = $this->validateDataApplications($request, $server);
if ($return instanceof JsonResponse) {
@@ -1938,7 +1941,7 @@ class ApplicationsController extends Controller
$application->isConfigurationChanged(true);
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -2736,7 +2739,7 @@ class ApplicationsController extends Controller
]);
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -3643,7 +3646,7 @@ class ApplicationsController extends Controller
$this->authorize('deploy', $application);
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -3841,7 +3844,7 @@ class ApplicationsController extends Controller
$this->authorize('deploy', $application);
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
@@ -182,6 +182,7 @@ class CloudProviderTokensController extends Controller
if (is_null($token)) {
return response()->json(['message' => 'Cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
return response()->json($this->removeSensitiveData($token));
}
@@ -248,6 +249,7 @@ class CloudProviderTokensController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$this->authorize('create', [CloudProviderToken::class]);
$return = validateIncomingRequest($request);
if ($return instanceof JsonResponse) {
@@ -399,6 +401,7 @@ class CloudProviderTokensController extends Controller
if (! $token) {
return response()->json(['message' => 'Cloud provider token not found.'], 404);
}
$this->authorize('update', $token);
$token->update(array_intersect_key($body, array_flip($allowedFields)));
@@ -480,6 +483,7 @@ class CloudProviderTokensController extends Controller
if (! $token) {
return response()->json(['message' => 'Cloud provider token not found.'], 404);
}
$this->authorize('delete', $token);
if ($token->hasServers()) {
return response()->json(['message' => 'Cannot delete token that is used by servers.'], 400);
@@ -550,9 +554,18 @@ class CloudProviderTokensController extends Controller
if (! $cloudToken) {
return response()->json(['message' => 'Cloud provider token not found.'], 404);
}
$this->authorize('view', $cloudToken);
$validation = $this->validateProviderToken($cloudToken->provider, $cloudToken->token);
auditLog('api.cloud_token.validated', [
'team_id' => $teamId,
'cloud_token_uuid' => $cloudToken->uuid,
'cloud_token_name' => $cloudToken->name,
'provider' => $cloudToken->provider,
'valid' => $validation['valid'],
]);
return response()->json([
'valid' => $validation['valid'],
'message' => $validation['valid'] ? 'Token is valid.' : $validation['error'],
@@ -15,7 +15,6 @@ use App\Models\Tag;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Http\Request;
use OpenApi\Attributes as OA;
use Visus\Cuid2\Cuid2;
class DeployController extends Controller
{
@@ -515,7 +514,7 @@ class DeployController extends Controller
if ($dockerTag !== null && $resource->build_pack !== 'dockerimage') {
return ['message' => 'docker_tag can only be used with Docker Image applications.', 'deployment_uuid' => null];
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $resource,
deployment_uuid: $deployment_uuid,
@@ -183,6 +183,7 @@ class GithubController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$this->authorize('create', [GithubApp::class]);
$return = validateIncomingRequest($request);
if ($return instanceof JsonResponse) {
return $return;
@@ -564,6 +565,7 @@ class GithubController extends Controller
$githubApp = GithubApp::where('id', $github_app_id)
->where('team_id', $teamId)
->firstOrFail();
$this->authorize('update', $githubApp);
// Define allowed fields for update
$allowedFields = [
@@ -737,6 +739,7 @@ class GithubController extends Controller
$githubApp = GithubApp::where('id', $github_app_id)
->where('team_id', $teamId)
->firstOrFail();
$this->authorize('delete', $githubApp);
// Check if the GitHub app is being used by any applications
if ($githubApp->applications->isNotEmpty()) {
@@ -116,6 +116,7 @@ class HetznerController extends Controller
if (! $token) {
return response()->json(['message' => 'Hetzner cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
try {
$hetznerService = new HetznerService($token->token);
@@ -237,6 +238,7 @@ class HetznerController extends Controller
if (! $token) {
return response()->json(['message' => 'Hetzner cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
try {
$hetznerService = new HetznerService($token->token);
@@ -336,6 +338,7 @@ class HetznerController extends Controller
if (! $token) {
return response()->json(['message' => 'Hetzner cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
try {
$hetznerService = new HetznerService($token->token);
@@ -445,6 +448,7 @@ class HetznerController extends Controller
if (! $token) {
return response()->json(['message' => 'Hetzner cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
try {
$hetznerService = new HetznerService($token->token);
@@ -550,6 +554,7 @@ class HetznerController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$this->authorize('create', [Server::class]);
$return = validateIncomingRequest($request);
if ($return instanceof JsonResponse) {
@@ -620,6 +625,7 @@ class HetznerController extends Controller
if (! $token) {
return response()->json(['message' => 'Hetzner cloud provider token not found.'], 404);
}
$this->authorize('view', $token);
// Validate private key
$privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first();
@@ -97,6 +97,7 @@ class ProjectController extends Controller
if (! $project) {
return response()->json(['message' => 'Project not found.'], 404);
}
$this->authorize('view', $project);
$project->load(['environments']);
@@ -233,6 +234,7 @@ class ProjectController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$this->authorize('create', [Project::class]);
$return = validateIncomingRequest($request);
if ($return instanceof JsonResponse) {
@@ -385,6 +387,7 @@ class ProjectController extends Controller
if (! $project) {
return response()->json(['message' => 'Project not found.'], 404);
}
$this->authorize('update', $project);
$project->update($request->only($allowedFields));
@@ -469,6 +472,7 @@ class ProjectController extends Controller
if (! $project) {
return response()->json(['message' => 'Project not found.'], 404);
}
$this->authorize('delete', $project);
if (! $project->isEmpty()) {
return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400);
}
@@ -652,6 +656,7 @@ class ProjectController extends Controller
if (! $project) {
return response()->json(['message' => 'Project not found.'], 404);
}
$this->authorize('update', $project);
$existingEnvironment = $project->environments()->where('name', $request->name)->first();
if ($existingEnvironment) {
@@ -746,6 +751,7 @@ class ProjectController extends Controller
if (! $environment) {
return response()->json(['message' => 'Environment not found.'], 404);
}
$this->authorize('delete', $environment);
if (! $environment->isEmpty()) {
return response()->json(['message' => 'Environment has resources, so it cannot be deleted.'], 400);
@@ -114,6 +114,7 @@ class SecurityController extends Controller
'message' => 'Private Key not found.',
], 404);
}
$this->authorize('view', $key);
return response()->json($this->removeSensitiveData($key));
}
@@ -180,6 +181,7 @@ class SecurityController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$this->authorize('create', [PrivateKey::class]);
$return = validateIncomingRequest($request);
if ($return instanceof JsonResponse) {
return $return;
@@ -342,6 +344,7 @@ class SecurityController extends Controller
'message' => 'Private Key not found.',
], 404);
}
$this->authorize('update', $foundKey);
$foundKey->update($request->only($allowedFields));
auditLog('api.private_key.updated', [
@@ -425,6 +428,7 @@ class SecurityController extends Controller
if (is_null($key)) {
return response()->json(['message' => 'Private Key not found.'], 404);
}
$this->authorize('delete', $key);
if ($key->isInUse()) {
return response()->json([
@@ -97,12 +97,12 @@ class SentinelController extends Controller
if ($this->shouldDispatchUpdate($server, $data)) {
PushServerUpdateJob::dispatch($server, $data);
}
auditLog('sentinel.metrics_pushed', [
'server_uuid' => $server->uuid,
'team_id' => $server->team_id,
]);
auditLog('sentinel.metrics_pushed', [
'server_uuid' => $server->uuid,
'team_id' => $server->team_id,
]);
}
return response()->json(['message' => 'ok'], 200);
}
@@ -156,6 +156,7 @@ class ServersController extends Controller
if (is_null($server)) {
return response()->json(['message' => 'Server not found.'], 404);
}
$this->authorize('view', $server);
if ($with_resources) {
$server['resources'] = $server->definedResources()->map(function ($resource) {
$payload = [
@@ -485,6 +486,7 @@ class ServersController extends Controller
if (is_null($teamId)) {
return invalidTokenResponse();
}
$this->authorize('create', [ModelsServer::class]);
$return = validateIncomingRequest($request);
if ($return instanceof JsonResponse) {
@@ -709,6 +711,7 @@ class ServersController extends Controller
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
}
$this->authorize('update', $server);
if ($request->proxy_type) {
$validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) {
return str($proxyType->value)->lower();
@@ -833,6 +836,7 @@ class ServersController extends Controller
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
}
$this->authorize('delete', $server);
$force = filter_var($request->query('force', false), FILTER_VALIDATE_BOOLEAN);
@@ -932,6 +936,7 @@ class ServersController extends Controller
if (! $server) {
return response()->json(['message' => 'Server not found.'], 404);
}
$this->authorize('update', $server);
ValidateServer::dispatch($server);
auditLog('api.server.validated', [
@@ -42,6 +42,10 @@ class ServicesController extends Controller
$this->exposeNestedServerSecrets($service);
}
if ($service->is_shown_once ?? false) {
$service->makeHidden(['value', 'real_value']);
}
return serializeApiResponse($service);
}
@@ -110,6 +110,7 @@ class TeamController extends Controller
if (is_null($team)) {
return response()->json(['message' => 'Team not found.'], 404);
}
$this->authorize('view', $team);
$team = $this->removeSensitiveData($team);
return response()->json(
@@ -168,6 +169,7 @@ class TeamController extends Controller
if (is_null($team)) {
return response()->json(['message' => 'Team not found.'], 404);
}
$this->authorize('view', $team);
$members = $team->members;
$members->makeHidden([
'pivot',
@@ -2,6 +2,7 @@
namespace App\Http\Controllers;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Routing\Controller as BaseController;
@@ -11,6 +12,8 @@ use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
class UploadController extends BaseController
{
use AuthorizesRequests;
private const MAX_BYTES = 10 * 1024 * 1024 * 1024; // 10 GiB
private const ALLOWED_EXTENSIONS = [
@@ -40,6 +43,8 @@ class UploadController extends BaseController
return response()->json(['error' => 'You do not have permission for this database'], 500);
}
$this->authorize('uploadBackup', $resource);
$chunk = $request->file('file');
$originalName = $chunk instanceof UploadedFile ? $chunk->getClientOriginalName() : null;
if (blank($originalName) || ! self::hasAllowedExtension($originalName)) {
+2 -3
View File
@@ -10,7 +10,6 @@ use App\Models\Application;
use App\Models\ApplicationPreview;
use Exception;
use Illuminate\Http\Request;
use Visus\Cuid2\Cuid2;
class Bitbucket extends Controller
{
@@ -141,7 +140,7 @@ class Bitbucket extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -192,7 +191,7 @@ class Bitbucket extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
+2 -3
View File
@@ -11,7 +11,6 @@ use App\Models\ApplicationPreview;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class Gitea extends Controller
{
@@ -127,7 +126,7 @@ class Gitea extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -194,7 +193,7 @@ class Gitea extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {
+2 -3
View File
@@ -17,7 +17,6 @@ use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class Github extends Controller
{
@@ -144,7 +143,7 @@ class Github extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -362,7 +361,7 @@ class Github extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
+2 -3
View File
@@ -11,7 +11,6 @@ use App\Models\ApplicationPreview;
use Exception;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
use Visus\Cuid2\Cuid2;
class Gitlab extends Controller
{
@@ -168,7 +167,7 @@ class Gitlab extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$result = queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
@@ -236,7 +235,7 @@ class Gitlab extends Controller
continue;
}
$deployment_uuid = new Cuid2;
$deployment_uuid = new_public_id();
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
if (! $found) {
if ($application->build_pack === 'dockercompose') {