Skip to content

Commit ddfa05c

Browse files
Merge pull request #389 from JLG-WOCFR-DEV/codex/mask-secrets-in-s3-and-google-sheets-settings
Mask sensitive credentials in export REST endpoints
2 parents f192a86 + b4e5dfc commit ddfa05c

File tree

7 files changed

+366
-10
lines changed

7 files changed

+366
-10
lines changed

liens-morts-detector-jlg/assets/js/blc-admin-scripts.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,41 @@ jQuery(document).ready(function($) {
192192
})();
193193

194194
window.blcAdmin = window.blcAdmin || {};
195+
window.blcAdmin.integrations = window.blcAdmin.integrations || {};
196+
197+
window.blcAdmin.integrations.hasStoredCredential = function(settings, key) {
198+
if (!settings || typeof key !== 'string') {
199+
return false;
200+
}
201+
202+
var flag = 'has_' + key;
203+
if (Object.prototype.hasOwnProperty.call(settings, flag)) {
204+
return !!settings[flag];
205+
}
206+
207+
var value = settings[key];
208+
209+
return typeof value === 'string' && value !== '';
210+
};
211+
212+
window.blcAdmin.integrations.getMaskedValue = function(settings, key, placeholder) {
213+
var hasFlag = false;
214+
215+
if (settings && typeof key === 'string') {
216+
var flag = 'has_' + key;
217+
hasFlag = Object.prototype.hasOwnProperty.call(settings, flag);
218+
}
219+
220+
if (hasFlag && window.blcAdmin.integrations.hasStoredCredential(settings, key)) {
221+
return typeof placeholder === 'string' && placeholder !== '' ? placeholder : '••••••••';
222+
}
223+
224+
if (settings && typeof settings[key] === 'string') {
225+
return settings[key];
226+
}
227+
228+
return '';
229+
};
195230

196231
var toast = (function() {
197232
var $container = null;

liens-morts-detector-jlg/includes/blc-google-sheets.php

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,30 @@ function blc_update_google_sheets_settings(array $settings)
130130
}
131131
}
132132

133+
if (!function_exists('blc_google_sheets_prepare_settings_for_response')) {
134+
/**
135+
* Prepare Google Sheets settings returned by REST endpoints by masking secrets.
136+
*
137+
* @param array<string,mixed> $settings
138+
*
139+
* @return array<string,mixed>
140+
*/
141+
function blc_google_sheets_prepare_settings_for_response(array $settings)
142+
{
143+
$prepared = $settings;
144+
145+
$prepared['has_client_secret'] = $settings['client_secret'] !== '';
146+
$prepared['has_access_token'] = $settings['access_token'] !== '';
147+
$prepared['has_refresh_token'] = $settings['refresh_token'] !== '';
148+
149+
$prepared['client_secret'] = null;
150+
$prepared['access_token'] = null;
151+
$prepared['refresh_token'] = null;
152+
153+
return $prepared;
154+
}
155+
}
156+
133157
if (!function_exists('blc_is_google_sheets_integration_enabled')) {
134158
/**
135159
* Determine if the Google Sheets connector is configured.
@@ -588,7 +612,7 @@ function blc_rest_manage_google_sheets_permissions()
588612
*/
589613
function blc_rest_get_google_sheets_settings()
590614
{
591-
return rest_ensure_response(blc_get_google_sheets_settings());
615+
return rest_ensure_response(blc_google_sheets_prepare_settings_for_response(blc_get_google_sheets_settings()));
592616
}
593617
}
594618

@@ -618,7 +642,11 @@ function blc_rest_update_google_sheets_settings(\WP_REST_Request $request)
618642
}
619643

620644
foreach (['spreadsheet_id', 'client_id', 'client_secret'] as $key) {
621-
if (isset($params[$key])) {
645+
if (array_key_exists($key, $params)) {
646+
if ($key === 'client_secret' && $params[$key] === null) {
647+
continue;
648+
}
649+
622650
$current[$key] = blc_google_sheets_sanitize_text($params[$key]);
623651
}
624652
}
@@ -636,7 +664,7 @@ function blc_rest_update_google_sheets_settings(\WP_REST_Request $request)
636664

637665
$updated = blc_update_google_sheets_settings($current);
638666

639-
return rest_ensure_response($updated);
667+
return rest_ensure_response(blc_google_sheets_prepare_settings_for_response($updated));
640668
}
641669
}
642670

@@ -685,7 +713,7 @@ function blc_rest_store_google_sheets_token(\WP_REST_Request $request)
685713

686714
$updated = blc_update_google_sheets_settings($settings);
687715

688-
return rest_ensure_response($updated);
716+
return rest_ensure_response(blc_google_sheets_prepare_settings_for_response($updated));
689717
}
690718
}
691719

liens-morts-detector-jlg/includes/blc-s3-exports.php

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,30 @@ function blc_update_s3_settings(array $settings)
169169
}
170170
}
171171

172+
if (!function_exists('blc_s3_prepare_settings_for_response')) {
173+
/**
174+
* Prepare settings returned by REST endpoints by masking credentials.
175+
*
176+
* @param array<string,mixed> $settings
177+
*
178+
* @return array<string,mixed>
179+
*/
180+
function blc_s3_prepare_settings_for_response(array $settings)
181+
{
182+
$prepared = $settings;
183+
184+
$prepared['has_access_key_id'] = $settings['access_key_id'] !== '';
185+
$prepared['has_secret_access_key'] = $settings['secret_access_key'] !== '';
186+
$prepared['has_session_token'] = $settings['session_token'] !== '';
187+
188+
$prepared['access_key_id'] = null;
189+
$prepared['secret_access_key'] = null;
190+
$prepared['session_token'] = null;
191+
192+
return $prepared;
193+
}
194+
}
195+
172196
if (!function_exists('blc_is_s3_integration_enabled')) {
173197
/**
174198
* Determine if the S3 connector is configured.
@@ -669,7 +693,7 @@ function blc_rest_manage_s3_permissions()
669693
*/
670694
function blc_rest_get_s3_settings()
671695
{
672-
return rest_ensure_response(blc_get_s3_settings());
696+
return rest_ensure_response(blc_s3_prepare_settings_for_response(blc_get_s3_settings()));
673697
}
674698
}
675699

@@ -699,22 +723,26 @@ function blc_rest_update_s3_settings(\WP_REST_Request $request)
699723
}
700724

701725
foreach (['bucket', 'region'] as $key) {
702-
if (isset($params[$key])) {
726+
if (array_key_exists($key, $params)) {
703727
$current[$key] = blc_s3_sanitize_text($params[$key]);
704728
}
705729
}
706730

707-
if (isset($params['endpoint'])) {
731+
if (array_key_exists('endpoint', $params)) {
708732
$current['endpoint'] = blc_s3_sanitize_text($params['endpoint'], ['allow_url' => true]);
709733
}
710734

711735
foreach (['access_key_id', 'secret_access_key', 'session_token'] as $credential_key) {
712-
if (isset($params[$credential_key])) {
736+
if (array_key_exists($credential_key, $params)) {
737+
if ($params[$credential_key] === null) {
738+
continue;
739+
}
740+
713741
$current[$credential_key] = blc_s3_sanitize_credential($params[$credential_key]);
714742
}
715743
}
716744

717-
if (isset($params['object_prefix'])) {
745+
if (array_key_exists('object_prefix', $params)) {
718746
$current['object_prefix'] = blc_s3_normalize_object_prefix($params['object_prefix']);
719747
}
720748

@@ -729,7 +757,7 @@ function blc_rest_update_s3_settings(\WP_REST_Request $request)
729757

730758
$updated = blc_update_s3_settings($current);
731759

732-
return rest_ensure_response($updated);
760+
return rest_ensure_response(blc_s3_prepare_settings_for_response($updated));
733761
}
734762
}
735763

tests/BlcGoogleSheetsConnectorTest.php

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ protected function setUp(): void
6767

6868
require_once __DIR__ . '/translation-stubs.php';
6969
require_once __DIR__ . '/wp-option-stubs.php';
70+
require_once __DIR__ . '/stubs/rest-request-stub.php';
7071

7172
if (!defined('ABSPATH')) {
7273
define('ABSPATH', __DIR__ . '/../');
@@ -225,6 +226,85 @@ public function test_handle_report_export_pushes_values_to_google_sheets(): void
225226
$this->assertSame(['dataset', 'url'], $payload['data'][0]['values'][0]);
226227
$this->assertSame(['link', 'https://example.com'], $payload['data'][0]['values'][1]);
227228
}
229+
230+
public function test_rest_get_settings_masks_google_credentials(): void
231+
{
232+
$this->options['blc_google_sheets_settings'] = [
233+
'enabled' => true,
234+
'spreadsheet_id'=> 'spreadsheet-123',
235+
'client_id' => 'client-1',
236+
'client_secret' => 'secret-1',
237+
'access_token' => 'token-1',
238+
'refresh_token' => 'refresh-1',
239+
];
240+
241+
$response = \blc_rest_get_google_sheets_settings();
242+
243+
$this->assertNull($response['client_secret']);
244+
$this->assertNull($response['access_token']);
245+
$this->assertNull($response['refresh_token']);
246+
$this->assertTrue($response['has_client_secret']);
247+
$this->assertTrue($response['has_access_token']);
248+
$this->assertTrue($response['has_refresh_token']);
249+
}
250+
251+
public function test_rest_update_settings_preserves_client_secret_when_omitted(): void
252+
{
253+
$this->options['blc_google_sheets_settings'] = [
254+
'enabled' => true,
255+
'spreadsheet_id'=> 'spreadsheet-123',
256+
'client_id' => 'client-1',
257+
'client_secret' => 'secret-1',
258+
];
259+
260+
$request = new \WP_REST_Request(
261+
['spreadsheet_id' => 'updated-sheet'],
262+
['spreadsheet_id' => 'updated-sheet']
263+
);
264+
265+
$response = \blc_rest_update_google_sheets_settings($request);
266+
267+
$settings = \blc_get_google_sheets_settings();
268+
269+
$this->assertSame('updated-sheet', $settings['spreadsheet_id']);
270+
$this->assertSame('secret-1', $settings['client_secret']);
271+
$this->assertNull($response['client_secret']);
272+
$this->assertTrue($response['has_client_secret']);
273+
}
274+
275+
public function test_rest_store_token_masks_response(): void
276+
{
277+
$this->options['blc_google_sheets_settings'] = [
278+
'enabled' => true,
279+
'spreadsheet_id'=> 'spreadsheet-123',
280+
'client_id' => 'client-1',
281+
'client_secret' => 'secret-1',
282+
];
283+
284+
$request = new \WP_REST_Request(
285+
[
286+
'access_token' => 'new-token',
287+
'refresh_token' => 'refresh-2',
288+
'expires_in' => 1800,
289+
],
290+
[
291+
'access_token' => 'new-token',
292+
'refresh_token' => 'refresh-2',
293+
'expires_in' => 1800,
294+
]
295+
);
296+
297+
$response = \blc_rest_store_google_sheets_token($request);
298+
299+
$settings = \blc_get_google_sheets_settings();
300+
301+
$this->assertSame('new-token', $settings['access_token']);
302+
$this->assertSame('refresh-2', $settings['refresh_token']);
303+
$this->assertNull($response['access_token']);
304+
$this->assertNull($response['refresh_token']);
305+
$this->assertTrue($response['has_access_token']);
306+
$this->assertTrue($response['has_refresh_token']);
307+
}
228308
}
229309

230310
}

tests/BlcS3ExportConnectorTest.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ protected function setUp(): void
6969

7070
require_once __DIR__ . '/translation-stubs.php';
7171
require_once __DIR__ . '/wp-option-stubs.php';
72+
require_once __DIR__ . '/stubs/rest-request-stub.php';
7273

7374
if (!defined('ABSPATH')) {
7475
define('ABSPATH', __DIR__ . '/../');
@@ -191,6 +192,93 @@ public function test_handle_report_export_stores_error_on_failure(): void
191192
$this->assertSame('s3_error', $settings['last_error_code']);
192193
$this->assertSame('Unable to upload', $settings['last_error']);
193194
}
195+
196+
public function test_rest_get_settings_masks_s3_credentials(): void
197+
{
198+
$this->options['blc_s3_export_settings'] = [
199+
'enabled' => true,
200+
'bucket' => 'reports-bucket',
201+
'region' => 'eu-west-3',
202+
'access_key_id' => 'AKIAIOSAMPLE',
203+
'secret_access_key' => 'very-secret',
204+
'session_token' => 'temporary-token',
205+
];
206+
207+
$response = \blc_rest_get_s3_settings();
208+
209+
$this->assertNull($response['access_key_id']);
210+
$this->assertNull($response['secret_access_key']);
211+
$this->assertNull($response['session_token']);
212+
$this->assertTrue($response['has_access_key_id']);
213+
$this->assertTrue($response['has_secret_access_key']);
214+
$this->assertTrue($response['has_session_token']);
215+
}
216+
217+
public function test_rest_update_settings_preserves_credentials_when_omitted(): void
218+
{
219+
$this->options['blc_s3_export_settings'] = [
220+
'enabled' => true,
221+
'bucket' => 'reports-bucket',
222+
'region' => 'eu-west-3',
223+
'access_key_id' => 'AKIAIOSAMPLE',
224+
'secret_access_key' => 'very-secret',
225+
];
226+
227+
$request = new \WP_REST_Request(
228+
['bucket' => 'updated-bucket'],
229+
['bucket' => 'updated-bucket']
230+
);
231+
232+
$response = \blc_rest_update_s3_settings($request);
233+
234+
$settings = \blc_get_s3_settings();
235+
236+
$this->assertSame('updated-bucket', $settings['bucket']);
237+
$this->assertSame('AKIAIOSAMPLE', $settings['access_key_id']);
238+
$this->assertSame('very-secret', $settings['secret_access_key']);
239+
$this->assertTrue($response['has_access_key_id']);
240+
$this->assertTrue($response['has_secret_access_key']);
241+
$this->assertNull($response['access_key_id']);
242+
$this->assertNull($response['secret_access_key']);
243+
}
244+
245+
public function test_rest_update_settings_accepts_new_credentials(): void
246+
{
247+
$this->options['blc_s3_export_settings'] = [
248+
'enabled' => true,
249+
'bucket' => 'reports-bucket',
250+
'region' => 'eu-west-3',
251+
'access_key_id' => 'AKIAIOSAMPLE',
252+
'secret_access_key' => 'very-secret',
253+
];
254+
255+
$request = new \WP_REST_Request(
256+
[
257+
'access_key_id' => 'NEWKEY',
258+
'secret_access_key' => 'new-secret',
259+
'session_token' => 'new-token',
260+
],
261+
[
262+
'access_key_id' => 'NEWKEY',
263+
'secret_access_key' => 'new-secret',
264+
'session_token' => 'new-token',
265+
]
266+
);
267+
268+
$response = \blc_rest_update_s3_settings($request);
269+
270+
$settings = \blc_get_s3_settings();
271+
272+
$this->assertSame('NEWKEY', $settings['access_key_id']);
273+
$this->assertSame('new-secret', $settings['secret_access_key']);
274+
$this->assertSame('new-token', $settings['session_token']);
275+
$this->assertTrue($response['has_access_key_id']);
276+
$this->assertTrue($response['has_secret_access_key']);
277+
$this->assertTrue($response['has_session_token']);
278+
$this->assertNull($response['access_key_id']);
279+
$this->assertNull($response['secret_access_key']);
280+
$this->assertNull($response['session_token']);
281+
}
194282
}
195283

196284
}

0 commit comments

Comments
 (0)