| @@ -16,13 +16,15 @@ | ||
| 16 | 16 | use Exception; |
| 17 | 17 | use WC_Customer; |
| 18 | 18 | use WC_REST_Customers_Controller; |
| 19 | 19 | use WCPOS\WooCommercePOS\Logger; |
| 20 | +use WCPOS\WooCommercePOS\Services\Customer_Account_Guard; | |
| 20 | 21 | use WCPOS\WooCommercePOS\Services\Settings as SettingsService; |
| 21 | 22 | use WCPOS\WooCommercePOS\Services\Tax_Id_Reader; |
| 22 | 23 | use WCPOS\WooCommercePOS\Services\Tax_Id_Types; |
| 23 | 24 | use WCPOS\WooCommercePOS\Services\Tax_Id_Writer; |
| 24 | 25 | use WCPOS\WooCommercePOS\Sync\Collection_Rules; |
| 26 | +use WCPOS\WooCommercePOS\Sync\Meta_Normalizer; | |
| 25 | 27 | use WP_Error; |
| 26 | 28 | use WP_REST_Request; |
| 27 | 29 | use WP_REST_Response; |
| 28 | 30 | use WP_User; |
| @@ -157,10 +159,12 @@ | ||
| 157 | 159 | |
| 158 | 160 | /** |
| 159 | 161 | * Check if a given request has access to update a customer. |
| 160 | 162 | * |
| 161 | - * WC checks edit_users. The POS fallback also checks edit_users so the | |
| 162 | - * Access settings page toggle controls this behaviour. | |
| 163 | + * WCPOS never widens WooCommerce's credential fence, which refuses | |
| 164 | + * email/password changes on non-customer roles. The guard additionally | |
| 165 | + * keeps non-admins off staff accounts, testing capabilities rather than | |
| 166 | + * WooCommerce's first-role-only test. | |
| 163 | 167 | * |
| 164 | 168 | * @param WP_REST_Request $request Full details about the request. |
| 165 | 169 | * |
| 166 | 170 | * @return WP_Error|bool |
| @@ -165,15 +169,62 @@ | ||
| 165 | 169 | * |
| 166 | 170 | * @return WP_Error|bool |
| 167 | 171 | */ |
| 168 | 172 | public function update_item_permissions_check( $request ) { |
| 169 | - $permission = parent::update_item_permissions_check( $request ); | |
| 173 | + return $this->wcpos_guarded_permissions_check( | |
| 174 | + (int) $request['id'], | |
| 175 | + function () use ( $request ) { | |
| 176 | + return parent::update_item_permissions_check( $request ); | |
| 177 | + } | |
| 178 | + ); | |
| 179 | + } | |
| 170 | 180 | |
| 171 | - if ( is_wp_error( $permission ) && current_user_can( 'edit_users' ) ) { | |
| 172 | - return true; | |
| 181 | + /** | |
| 182 | + * Check if a given request has access to delete a customer. | |
| 183 | + * | |
| 184 | + * WooCommerce refuses deleting a user whose role is outside its allowed | |
| 185 | + * list, but it reads only the FIRST role, so an administrator who also | |
| 186 | + * holds the customer role is deleted by any POS user with delete_users. | |
| 187 | + * The guard closes that by capability. | |
| 188 | + * | |
| 189 | + * @param WP_REST_Request $request Full details about the request. | |
| 190 | + * | |
| 191 | + * @return WP_Error|bool | |
| 192 | + */ | |
| 193 | + public function delete_item_permissions_check( $request ) { | |
| 194 | + return $this->wcpos_guarded_permissions_check( | |
| 195 | + (int) $request['id'], | |
| 196 | + function () use ( $request ) { | |
| 197 | + return parent::delete_item_permissions_check( $request ); | |
| 198 | + } | |
| 199 | + ); | |
| 200 | + } | |
| 201 | + | |
| 202 | + /** | |
| 203 | + * Run WooCommerce's own check behind the staff account guard. | |
| 204 | + * | |
| 205 | + * The guard runs first and can only refuse. When it clears the target, | |
| 206 | + * that target's roles are allowed through WooCommerce's shop_manager | |
| 207 | + * role-name restriction for the duration of the check, so a cleared | |
| 208 | + * subscriber or membership-plugin role is judged by capability. | |
| 209 | + * | |
| 210 | + * @param int $target_id Target user ID. | |
| 211 | + * @param callable $check Returns WooCommerce's verdict. | |
| 212 | + * | |
| 213 | + * @return WP_Error|bool | |
| 214 | + */ | |
| 215 | + private function wcpos_guarded_permissions_check( int $target_id, callable $check ) { | |
| 216 | + if ( ! Customer_Account_Guard::can_modify( get_current_user_id(), $target_id ) ) { | |
| 217 | + return Customer_Account_Guard::denial(); | |
| 173 | 218 | } |
| 174 | 219 | |
| 175 | - return $permission; | |
| 220 | + $restore = Customer_Account_Guard::allow_target_roles( $target_id ); | |
| 221 | + | |
| 222 | + try { | |
| 223 | + return $check(); | |
| 224 | + } finally { | |
| 225 | + $restore(); | |
| 226 | + } | |
| 176 | 227 | } |
| 177 | 228 | |
| 178 | 229 | /** |
| 179 | 230 | * Add extra fields to WP_REST_Controller::get_collection_params(). |
| @@ -390,9 +441,20 @@ | ||
| 390 | 441 | |
| 391 | 442 | $filtered_meta_data = array_filter( |
| 392 | 443 | $raw_meta_data, |
| 393 | 444 | function ( $meta ) { |
| 394 | - return ! is_protected_meta( $meta->key, 'user' ); | |
| 445 | + if ( is_protected_meta( $meta->key, 'user' ) ) { | |
| 446 | + return false; | |
| 447 | + } | |
| 448 | + // A single enormous value fatals the response encoder no matter how few | |
| 449 | + // entries the customer has; same budget as the v2 sync lane. | |
| 450 | + if ( Meta_Normalizer::exceeds_value_budget( $meta->value ) ) { | |
| 451 | + Meta_Normalizer::note_oversized_meta( (string) $meta->key, (int) $meta->id ); | |
| 452 | + | |
| 453 | + return false; | |
| 454 | + } | |
| 455 | + | |
| 456 | + return true; | |
| 395 | 457 | } |
| 396 | 458 | ); |
| 397 | 459 | |
| 398 | 460 | // Convert to WC REST API expected format. |