| @@ -7,8 +7,9 @@ | ||
| 7 | 7 | |
| 8 | 8 | namespace WCPOS\WooCommercePOS\API\V2; |
| 9 | 9 | |
| 10 | 10 | use WCPOS\WooCommercePOS\API\V2\Writers\Collection_Writer_Resolver; |
| 11 | +use WCPOS\WooCommercePOS\Services\Customer_Account_Guard; | |
| 11 | 12 | use WCPOS\WooCommercePOS\Services\Tax_Id_Types; |
| 12 | 13 | use WCPOS\WooCommercePOS\Sync\Api; |
| 13 | 14 | use WCPOS\WooCommercePOS\Sync\Collections; |
| 14 | 15 | use WCPOS\WooCommercePOS\Sync\Endpoint_Permissions; |
| @@ -43,8 +44,15 @@ | ||
| 43 | 44 | * live in an injected mutation store, so the apply logic is unit-testable with a |
| 44 | 45 | * fake store + a stubbed `rest_do_request`. |
| 45 | 46 | */ |
| 46 | 47 | class Write_Controller extends WP_REST_Controller { |
| 48 | + /** | |
| 49 | + * True while wc_rest_check_user_permissions() is re-run for a cleared target. | |
| 50 | + * | |
| 51 | + * @var bool | |
| 52 | + */ | |
| 53 | + private $rejudging_user_target = false; | |
| 54 | + | |
| 47 | 55 | // Our gate (capability + F13 health); forwarded writes scope the client-tier grant below. |
| 48 | 56 | use Endpoint_Permissions; |
| 49 | 57 | |
| 50 | 58 | |
| @@ -759,10 +767,43 @@ | ||
| 759 | 767 | } |
| 760 | 768 | } |
| 761 | 769 | |
| 762 | 770 | /** |
| 763 | - * Authorize proxied catalog mutations for POS users. | |
| 771 | + * Judge a customer edit or delete the way V1\Customers_Controller does. | |
| 764 | 772 | * |
| 773 | + * The staff guard runs first and is final. A target it has cleared is then | |
| 774 | + * re-judged by WooCommerce with the target's own roles allowed through the | |
| 775 | + * shop_manager role-name restriction, so a shop manager can edit a subscriber | |
| 776 | + * from a current app exactly as from the legacy route. WooCommerce's | |
| 777 | + * credential fence is untouched: it runs in the controller, not here. | |
| 778 | + * | |
| 779 | + * @param bool $permission WooCommerce's verdict so far. | |
| 780 | + * @param string $context 'edit' or 'delete'. | |
| 781 | + * @param int $target_id Target user ID. | |
| 782 | + */ | |
| 783 | + private function check_user_permission( bool $permission, string $context, int $target_id ): bool { | |
| 784 | + if ( $this->rejudging_user_target ) { | |
| 785 | + return $permission; | |
| 786 | + } | |
| 787 | + if ( ! Customer_Account_Guard::can_modify( get_current_user_id(), $target_id ) ) { | |
| 788 | + return false; | |
| 789 | + } | |
| 790 | + if ( $permission ) { | |
| 791 | + return true; | |
| 792 | + } | |
| 793 | + $this->rejudging_user_target = true; | |
| 794 | + $restore = Customer_Account_Guard::allow_target_roles( $target_id ); | |
| 795 | + try { | |
| 796 | + return (bool) wc_rest_check_user_permissions( $context, $target_id ); | |
| 797 | + } finally { | |
| 798 | + $restore(); | |
| 799 | + $this->rejudging_user_target = false; | |
| 800 | + } | |
| 801 | + } | |
| 802 | + | |
| 803 | + /** | |
| 804 | + * Authorize proxied mutations for POS users while protecting staff accounts. | |
| 805 | + * | |
| 765 | 806 | * This filter is attached only while a sync push is forwarded to wc/v3, so |
| 766 | 807 | * direct WooCommerce requests keep their normal permission checks. |
| 767 | 808 | * |
| 768 | 809 | * @param bool $permission The current permission. |
| @@ -772,8 +813,14 @@ | ||
| 772 | 813 | * |
| 773 | 814 | * @return bool |
| 774 | 815 | */ |
| 775 | 816 | public function wcpos_check_permissions( $permission, $context, $object_id, $post_type ) { |
| 817 | + // Customer edits/deletes: the staff guard is final, then a cleared target | |
| 818 | + // is judged by WooCommerce the same way the v1 controller judges it. | |
| 819 | + if ( 'user' === $post_type && (int) $object_id > 0 && \in_array( $context, array( 'edit', 'delete' ), true ) ) { | |
| 820 | + return $this->check_user_permission( (bool) $permission, $context, (int) $object_id ); | |
| 821 | + } | |
| 822 | + | |
| 776 | 823 | // Catalog and coupon WRITES require the user's real WooCommerce |
| 777 | 824 | // capabilities — no POS-tier widening. The cashier role is deliberately |
| 778 | 825 | // read-only on catalog (Activator), and a blanket grant here handed |
| 779 | 826 | // every POS user product deletion and coupon minting. Product decision |