fix(2meet-data-optimizer): keep registered fields out of the public listings REST endpoints
Anti-EAV Lint + Quality Gate / anti-eav-lint (pull_request) Successful in 15s
Tests / PHP Lint (pull_request) Successful in 17s
Tests / Unit Tests (pull_request) Successful in 51s
Tests / Integration Tests (pull_request) Successful in 1m22s
Tests / PHPCS (pull_request) Successful in 1m12s
Tests / PHPStan (pull_request) Successful in 1m10s

- show_in_rest defaulted to true, so GET /wpdo/v1/listings and /listings/{id} (public by design) returned every registered hot / cold field to anonymous visitors — e.g. infocards tmeetic_ig_access_token_enc / _token_expiry / _user_id and WooCommerce _purchase_note (confirmed on dev36)
- Fields are private unless registered with show_in_rest => true; protected (underscore) and credential-like keys (token, secret, password, api key, private key, credential, salt, nonce) are never exposed
- Zone A list path no longer returns whole rows when nothing is public, and filters / orderby accept public columns only
- Version 1.1.1; CHANGELOG, readme
- Verify: unit 690 (+3), integration 525 (+3 private-column tests, RED on 1.1.0), live suite rest 2/2 (RED on 1.1.0), all live 49/49, phpcs 0, stan OK
This commit is contained in:
2026-09-26 22:22:40 +08:00
parent abbdb39acc
commit 336664473b
9 changed files with 236 additions and 12 deletions
+59 -1
View File
@@ -95,11 +95,13 @@ class RestApiTest extends TestCase {
'zone' => 'hot',
'column' => 'hp_price',
'type' => 'decimal',
'show_in_rest' => true,
] );
$registry->register( 'test', [
'post_type' => 'hp_listing',
'meta_key' => 'hp_description',
'zone' => 'cold',
'show_in_rest' => true,
] );
$req = new WP_REST_Request( 'GET', '/wpdo/v1/listings/42' );
@@ -115,6 +117,62 @@ class RestApiTest extends TestCase {
$this->assertSame( 'Test desc', $data['hp_description'] );
}
// ── public field exposure (1.1.1) ─────────────────────────────────────────
/**
* Register fields on hp_listing #43 and GET it anonymously.
*
* @param array<int, array<string, mixed>> $fields Field configs (post_type added).
* @param array<string, string> $meta Stored meta.
* @return array<string, mixed> Response data.
*/
private function listing_with( array $fields, array $meta ): array {
$GLOBALS['_wp_post_types'][43] = 'hp_listing';
$GLOBALS['_wp_postmeta'][43] = $meta;
foreach ( $fields as $field ) {
WPDO_Schema_Registry::instance()->register( 'test', $field + array( 'post_type' => 'hp_listing' ) );
}
$req = new WP_REST_Request( 'GET', '/wpdo/v1/listings/43' );
$req->set_param( 'id', 43 );
return $this->api->get_listing( $req )->get_data();
}
public function test_registered_fields_are_not_public_unless_declared(): void {
$data = $this->listing_with(
array(
array( 'meta_key' => 'hp_price', 'zone' => 'hot', 'column' => 'hp_price' ),
array( 'meta_key' => 'hp_notes', 'zone' => 'cold' ),
),
array( 'hp_price' => '500', 'hp_notes' => 'internal' )
);
$this->assertSame( array( 'id', 'post_type' ), array_keys( $data ) );
}
public function test_protected_and_credential_keys_stay_private_even_when_declared_public(): void {
$data = $this->listing_with(
array(
array( 'meta_key' => 'hp_price', 'zone' => 'hot', 'column' => 'hp_price', 'show_in_rest' => true ),
array( 'meta_key' => '_purchase_note', 'zone' => 'cold', 'show_in_rest' => true ),
array( 'meta_key' => 'tmeetic_ig_access_token_enc', 'zone' => 'cold', 'show_in_rest' => true ),
array( 'meta_key' => 'hp_api_key', 'zone' => 'hot', 'column' => 'hp_api_key', 'show_in_rest' => true ),
array( 'meta_key' => 'hp_client_secret', 'zone' => 'cold', 'show_in_rest' => true ),
),
array( 'hp_price' => '500', '_purchase_note' => 'x', 'tmeetic_ig_access_token_enc' => 'y', 'hp_api_key' => 'z', 'hp_client_secret' => 'w' )
);
$this->assertSame( array( 'id', 'post_type', 'hp_price' ), array_keys( $data ) );
}
public function test_rest_visible_columns_exclude_protected_and_credential_keys(): void {
$registry = WPDO_Schema_Registry::instance();
$registry->register( 'test', array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_price', 'zone' => 'hot', 'column' => 'hp_price', 'show_in_rest' => true ) );
$registry->register( 'test', array( 'post_type' => 'hp_listing', 'meta_key' => '_hp_token', 'zone' => 'hot', 'column' => 'hp_token', 'show_in_rest' => true ) );
$registry->register( 'test', array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_draft', 'zone' => 'hot', 'column' => 'hp_draft' ) );
$this->assertSame( array( 'hp_price' ), $registry->get_rest_visible_hot_columns( 'hp_listing' ) );
}
// ── get_stats ─────────────────────────────────────────────────────────────
public function test_get_stats_404_when_post_not_found(): void {
@@ -185,7 +243,7 @@ class RestApiTest extends TestCase {
public function test_listings_fallback_only_returns_show_in_rest_columns(): void {
$registry = WPDO_Schema_Registry::instance();
$registry->register( 'unit_rest', array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_price', 'zone' => 'hot', 'column' => 'hp_price' ) );
$registry->register( 'unit_rest', array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_price', 'zone' => 'hot', 'column' => 'hp_price', 'show_in_rest' => true ) );
$registry->register( 'unit_rest', array( 'post_type' => 'hp_listing', 'meta_key' => 'hp_secret', 'zone' => 'hot', 'column' => 'hp_secret', 'show_in_rest' => false ) );
$post = new stdClass();