Skip to content

Commit 6f52f7a

Browse files
authored
fix(api): hide sensitive fields by default, expose via makeVisible for privileged tokens (#9893)
2 parents 3324999 + ff976a1 commit 6f52f7a

50 files changed

Lines changed: 2320 additions & 66 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/Http/Controllers/Api/ApplicationsController.php

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@
3333

3434
class ApplicationsController extends Controller
3535
{
36+
private function exposeFileStorageContentIfAllowed(LocalFileVolume|LocalPersistentVolume $storage): LocalFileVolume|LocalPersistentVolume
37+
{
38+
if (request()->attributes->get('can_read_sensitive', false) === true) {
39+
$storage->makeVisible(['content']);
40+
}
41+
42+
return $storage;
43+
}
44+
3645
private function removeSensitiveData($application)
3746
{
3847
$application->makeHidden([
@@ -41,8 +50,8 @@ private function removeSensitiveData($application)
4150
'resourceable_id',
4251
'resourceable_type',
4352
]);
44-
if (request()->attributes->get('can_read_sensitive', false) === false) {
45-
$application->makeHidden([
53+
if (request()->attributes->get('can_read_sensitive', false) === true) {
54+
$application->makeVisible([
4655
'custom_labels',
4756
'dockerfile',
4857
'docker_compose',
@@ -51,10 +60,14 @@ private function removeSensitiveData($application)
5160
'manual_webhook_secret_gitea',
5261
'manual_webhook_secret_github',
5362
'manual_webhook_secret_gitlab',
54-
'private_key_id',
63+
'http_basic_auth_password',
5564
'value',
5665
'real_value',
57-
'http_basic_auth_password',
66+
]);
67+
$this->exposeNestedServerSecrets($application);
68+
} else {
69+
$application->makeHidden([
70+
'private_key_id',
5871
]);
5972
}
6073

@@ -65,6 +78,34 @@ private function removeSensitiveData($application)
6578
return serializeApiResponse($application);
6679
}
6780

81+
/**
82+
* Expose sensitive fields on eager-loaded nested Server + ServerSetting
83+
* relations for callers with the `read:sensitive` or `root` token ability.
84+
* Models hide these by default via $hidden; this re-exposes them per-request.
85+
*/
86+
private function exposeNestedServerSecrets($model): void
87+
{
88+
$server = $model->destination?->server ?? null;
89+
if (! $server) {
90+
return;
91+
}
92+
$server->makeVisible([
93+
'logdrain_axiom_api_key',
94+
'logdrain_newrelic_license_key',
95+
]);
96+
$settings = $server->settings ?? null;
97+
if ($settings) {
98+
$settings->makeVisible([
99+
'sentinel_token',
100+
'sentinel_custom_url',
101+
'logdrain_newrelic_license_key',
102+
'logdrain_axiom_api_key',
103+
'logdrain_custom_config',
104+
'logdrain_custom_config_parser',
105+
]);
106+
}
107+
}
108+
68109
#[OA\Get(
69110
summary: 'List',
70111
description: 'List all applications.',
@@ -117,8 +158,12 @@ public function applications(Request $request)
117158
}
118159

119160
$tagName = $request->query('tag');
161+
$applicationRelations = $request->attributes->get('can_read_sensitive', false) === true
162+
? ['destination.server.settings']
163+
: [];
120164

121165
$applications = Application::ownedByCurrentTeamAPI($teamId)
166+
->with($applicationRelations)
122167
->when($tagName, function ($query, $tagName) {
123168
$query->whereHas('tags', function ($query) use ($tagName) {
124169
$query->where('name', $tagName);
@@ -2003,7 +2048,7 @@ public function application_by_uuid(Request $request)
20032048
if (! $uuid) {
20042049
return response()->json(['message' => 'UUID is required.'], 400);
20052050
}
2006-
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
2051+
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first();
20072052
if (! $application) {
20082053
return response()->json(['message' => 'Application not found.'], 404);
20092054
}
@@ -2091,7 +2136,7 @@ public function logs_by_uuid(Request $request)
20912136
if (! $uuid) {
20922137
return response()->json(['message' => 'UUID is required.'], 400);
20932138
}
2094-
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
2139+
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first();
20952140
if (! $application) {
20962141
return response()->json(['message' => 'Application not found.'], 404);
20972142
}
@@ -2185,7 +2230,7 @@ public function delete_by_uuid(Request $request)
21852230
if (! $request->uuid) {
21862231
return response()->json(['message' => 'UUID is required.'], 404);
21872232
}
2188-
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
2233+
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first();
21892234

21902235
if (! $application) {
21912236
return response()->json([
@@ -2398,7 +2443,7 @@ public function update_by_uuid(Request $request)
23982443
return $return;
23992444
}
24002445

2401-
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->uuid)->first();
2446+
$application = Application::ownedByCurrentTeamAPI($teamId)->where('uuid', $request->route('uuid'))->first();
24022447
if (! $application) {
24032448
return response()->json([
24042449
'message' => 'Application not found',
@@ -3998,6 +4043,7 @@ public function storages(Request $request): JsonResponse
39984043

39994044
$persistentStorages = $application->persistentStorages->sortBy('id')->values();
40004045
$fileStorages = $application->fileStorages->sortBy('id')->values();
4046+
$fileStorages->each(fn (LocalFileVolume $storage) => $this->exposeFileStorageContentIfAllowed($storage));
40014047

40024048
return response()->json([
40034049
'persistent_storages' => $persistentStorages,
@@ -4212,7 +4258,7 @@ public function update_storage(Request $request): JsonResponse
42124258
'mount_path' => $storage->mount_path ?? null,
42134259
]);
42144260

4215-
return response()->json($storage);
4261+
return response()->json($this->exposeFileStorageContentIfAllowed($storage));
42164262
}
42174263

42184264
#[OA\Post(
@@ -4446,7 +4492,7 @@ public function create_storage(Request $request): JsonResponse
44464492
'mount_path' => $storage->mount_path,
44474493
]);
44484494

4449-
return response()->json($storage, 201);
4495+
return response()->json($this->exposeFileStorageContentIfAllowed($storage), 201);
44504496
}
44514497

44524498
#[OA\Delete(

app/Http/Controllers/Api/CloudProviderTokensController.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@ private function removeSensitiveData($token)
1616
{
1717
$token->makeHidden([
1818
'id',
19-
'token',
2019
]);
2120

21+
if (request()->attributes->get('can_read_sensitive', false) === true) {
22+
$token->makeVisible([
23+
'token',
24+
]);
25+
}
26+
2227
return serializeApiResponse($token);
2328
}
2429

app/Http/Controllers/Api/DatabasesController.php

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,35 +20,119 @@
2020
use App\Models\Server;
2121
use App\Models\StandalonePostgresql;
2222
use App\Support\ValidationPatterns;
23+
use Illuminate\Database\Eloquent\Model;
2324
use Illuminate\Http\JsonResponse;
2425
use Illuminate\Http\Request;
2526
use Illuminate\Support\Facades\DB;
2627
use OpenApi\Attributes as OA;
2728

2829
class DatabasesController extends Controller
2930
{
30-
private function removeSensitiveData($database)
31+
private function exposeFileStorageContentIfAllowed(LocalFileVolume|LocalPersistentVolume $storage): LocalFileVolume|LocalPersistentVolume
32+
{
33+
if (request()->attributes->get('can_read_sensitive', false) === true) {
34+
$storage->makeVisible(['content']);
35+
}
36+
37+
return $storage;
38+
}
39+
40+
private function removeSensitiveData($database, bool $loadNestedServerSecrets = false)
3141
{
3242
$database->makeHidden([
3343
'id',
3444
'laravel_through_key',
3545
]);
36-
if (request()->attributes->get('can_read_sensitive', false) === false) {
37-
$database->makeHidden([
46+
if (request()->attributes->get('can_read_sensitive', false) === true) {
47+
$database->makeVisible([
3848
'internal_db_url',
3949
'external_db_url',
50+
'init_scripts',
4051
'postgres_password',
4152
'dragonfly_password',
4253
'redis_password',
4354
'mongo_initdb_root_password',
4455
'keydb_password',
4556
'clickhouse_admin_password',
57+
'mysql_password',
58+
'mysql_root_password',
59+
'mariadb_password',
60+
'mariadb_root_password',
4661
]);
62+
$this->exposeNestedServerSecrets($database);
63+
} else {
64+
$this->hideNestedServerSecrets($database, $loadNestedServerSecrets);
4765
}
4866

4967
return serializeApiResponse($database);
5068
}
5169

70+
private function hideNestedServerSecrets(Model $model, bool $loadRelations = false): void
71+
{
72+
if ($loadRelations) {
73+
$server = data_get($model, 'destination.server');
74+
} else {
75+
if (! $model->relationLoaded('destination')) {
76+
return;
77+
}
78+
79+
$destination = $model->getRelation('destination');
80+
if (! $destination || ! $destination->relationLoaded('server')) {
81+
return;
82+
}
83+
84+
$server = $destination->getRelation('server');
85+
}
86+
87+
if (! $server) {
88+
return;
89+
}
90+
91+
$server->makeHidden([
92+
'logdrain_axiom_api_key',
93+
'logdrain_newrelic_license_key',
94+
]);
95+
96+
if ($loadRelations || $server->relationLoaded('settings')) {
97+
$server->settings->makeHidden([
98+
'sentinel_token',
99+
'sentinel_custom_url',
100+
'logdrain_newrelic_license_key',
101+
'logdrain_axiom_api_key',
102+
'logdrain_custom_config',
103+
'logdrain_custom_config_parser',
104+
]);
105+
}
106+
}
107+
108+
/**
109+
* Expose sensitive fields on eager-loaded nested Server + ServerSetting
110+
* relations for callers with the `read:sensitive` or `root` token ability.
111+
*/
112+
private function exposeNestedServerSecrets(Model $model): void
113+
{
114+
$server = $model->destination?->server;
115+
if ($server === null) {
116+
return;
117+
}
118+
119+
$server->makeVisible([
120+
'logdrain_axiom_api_key',
121+
'logdrain_newrelic_license_key',
122+
]);
123+
124+
if ($server->settings !== null) {
125+
$server->settings->makeVisible([
126+
'sentinel_token',
127+
'sentinel_custom_url',
128+
'logdrain_newrelic_license_key',
129+
'logdrain_axiom_api_key',
130+
'logdrain_custom_config',
131+
'logdrain_custom_config_parser',
132+
]);
133+
}
134+
}
135+
52136
#[OA\Get(
53137
summary: 'List',
54138
description: 'List all databases.',
@@ -85,8 +169,12 @@ public function databases(Request $request)
85169
}
86170
$projects = Project::where('team_id', $teamId)->get();
87171
$databases = collect();
172+
$databaseRelations = $request->attributes->get('can_read_sensitive', false) === true
173+
? ['destination.server.settings']
174+
: [];
175+
88176
foreach ($projects as $project) {
89-
$databases = $databases->merge($project->databases());
177+
$databases = $databases->merge($project->databases($databaseRelations));
90178
}
91179

92180
$databaseIds = $databases->pluck('id')->toArray();
@@ -228,7 +316,7 @@ public function database_by_uuid(Request $request)
228316

229317
$this->authorize('view', $database);
230318

231-
return response()->json($this->removeSensitiveData($database));
319+
return response()->json($this->removeSensitiveData($database, loadNestedServerSecrets: true));
232320
}
233321

234322
#[OA\Patch(
@@ -3080,8 +3168,8 @@ private function removeSensitiveEnvData($env)
30803168
'resourceable_id',
30813169
'resourceable_type',
30823170
]);
3083-
if (request()->attributes->get('can_read_sensitive', false) === false) {
3084-
$env->makeHidden([
3171+
if (request()->attributes->get('can_read_sensitive', false) === true) {
3172+
$env->makeVisible([
30853173
'value',
30863174
'real_value',
30873175
]);
@@ -3721,6 +3809,7 @@ public function storages(Request $request): JsonResponse
37213809

37223810
$persistentStorages = $database->persistentStorages->sortBy('id')->values();
37233811
$fileStorages = $database->fileStorages->sortBy('id')->values();
3812+
$fileStorages->each(fn (LocalFileVolume $storage) => $this->exposeFileStorageContentIfAllowed($storage));
37243813

37253814
return response()->json([
37263815
'persistent_storages' => $persistentStorages,
@@ -3959,7 +4048,7 @@ public function create_storage(Request $request): JsonResponse
39594048
'mount_path' => $storage->mount_path,
39604049
]);
39614050

3962-
return response()->json($storage, 201);
4051+
return response()->json($this->exposeFileStorageContentIfAllowed($storage), 201);
39634052
}
39644053

39654054
#[OA\Patch(
@@ -4166,7 +4255,7 @@ public function update_storage(Request $request): JsonResponse
41664255
'mount_path' => $storage->mount_path ?? null,
41674256
]);
41684257

4169-
return response()->json($storage);
4258+
return response()->json($this->exposeFileStorageContentIfAllowed($storage));
41704259
}
41714260

41724261
#[OA\Delete(

app/Http/Controllers/Api/DeployController.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ private function removeSensitiveData($deployment)
2424
$deployment->makeHidden([
2525
'logs',
2626
]);
27+
} else {
28+
$deployment->makeVisible([
29+
'logs',
30+
]);
2731
}
2832

2933
return serializeApiResponse($deployment);
@@ -698,6 +702,9 @@ public function get_application_deployments(Request $request)
698702
$this->authorize('view', $application);
699703

700704
$deployments = $application->deployments($skip, $take);
705+
if ($request->attributes->get('can_read_sensitive', false) === true) {
706+
$deployments['deployments']->each->makeVisible(['logs']);
707+
}
701708

702709
return response()->json($deployments);
703710
}

app/Http/Controllers/Api/GithubController.php

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,17 @@ class GithubController extends Controller
1717
{
1818
private function removeSensitiveData($githubApp)
1919
{
20-
$githubApp->makeHidden([
21-
'client_secret',
22-
'webhook_secret',
23-
]);
20+
if (request()->attributes->get('can_read_sensitive', false) === true) {
21+
$githubApp->makeVisible([
22+
'client_secret',
23+
'webhook_secret',
24+
]);
25+
} else {
26+
$githubApp->makeHidden([
27+
'client_secret',
28+
'webhook_secret',
29+
]);
30+
}
2431

2532
return serializeApiResponse($githubApp);
2633
}

0 commit comments

Comments
 (0)