PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.16
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.16
1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 1.9.13 All 162 releases
woocommerce-pos / includes / Services / Customer_Account_Guard.php

Customer_Account_Guard.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.16, at includes/Services/Customer_Account_Guard.php

134 lines 4.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Staff account protection for customer writes.
4 *
5 * @package WCPOS\WooCommercePOS
6 */
7
8 namespace WCPOS\WooCommercePOS\Services;
9
10 /**
11 * Decides which accounts a POS user may edit or delete.
12 *
13 * WooCommerce refuses credential changes on non-customer roles, but the old
14 * WCPOS edit_users fallback bypassed that refusal, and WooCommerce's own test
15 * reads only the target's FIRST role, so an administrator who also holds the
16 * customer role passes it. This guard tests capabilities instead: a POS user
17 * who is not an administrator may not edit or delete an account holding staff
18 * capabilities, whatever its roles say. "Staff" covers site and store
19 * administration, user management and an author seat in wp-admin, so editors
20 * and authors are fenced as well as administrators, shop managers and other
21 * cashiers. Reads are deliberately untouched — the POS customer space is every
22 * user on the site (#1379).
23 */
24 class Customer_Account_Guard {
25 /**
26 * Get the capabilities that identify protected staff accounts.
27 *
28 * @return array
29 */
30 public static function protected_capabilities(): array {
31 /*
32 * Filters the capabilities that mark an account as staff.
33 *
34 * A POS user who cannot manage_options may not edit or delete an account
35 * holding any of these. The default marks site and store administration
36 * (manage_options, manage_woocommerce), user management (edit_users, which
37 * the Cashier role holds) and an author seat in wp-admin (edit_posts:
38 * editors, authors and contributors). Narrowing the list moves a target into
39 * the cleared path. Cleared targets bypass WooCommerce's
40 * woocommerce_shop_manager_editable_roles role-name restriction before
41 * WooCommerce re-judges them.
42 *
43 * @param {array} $capabilities
44 * @returns {array} $capabilities
45 * @since 1.10.10
46 * @hook woocommerce_pos_protected_account_capabilities
47 */
48 $caps = apply_filters( 'woocommerce_pos_protected_account_capabilities', array( 'manage_options', 'manage_woocommerce', 'edit_users', 'edit_posts' ) );
49
50 return array_values( array_unique( array_filter( array_map( 'strval', (array) $caps ) ) ) );
51 }
52
53 /**
54 * Check staff protection before WooCommerce's own permission checks.
55 *
56 * Deny is the default for anything this method cannot resolve: it only ever
57 * removes permission, so a caller that reaches it with a bad id is refused
58 * rather than waved through.
59 *
60 * @param int $actor_id Acting user ID.
61 * @param int $target_id Target user ID.
62 *
63 * @return bool
64 */
65 public static function can_modify( int $actor_id, int $target_id ): bool {
66 if ( $actor_id < 1 || $target_id < 1 ) {
67 return false;
68 }
69 if ( user_can( $actor_id, 'manage_options' ) || $actor_id === $target_id ) {
70 return true;
71 }
72 $target = get_user_by( 'id', $target_id );
73 if ( ! $target ) {
74 return false;
75 }
76 foreach ( self::protected_capabilities() as $cap ) {
77 if ( user_can( $target, $cap ) ) {
78 return false;
79 }
80 }
81
82 return true;
83 }
84
85 /**
86 * Let WooCommerce judge a cleared target by capability rather than role name.
87 *
88 * WooCommerce restricts a shop_manager to editing users whose role is in
89 * `woocommerce_shop_manager_editable_roles` (default: customer only), so a
90 * subscriber or a membership plugin's own customer role is refused outright
91 * even though the POS lists it. Once can_modify() has cleared the target of
92 * every staff capability, that role-name test adds nothing, so allow the
93 * target's own roles for the duration of the check.
94 *
95 * Call only after can_modify() returned true, and always invoke the returned
96 * closure to remove the filter.
97 *
98 * @param int $target_id Target user ID, already cleared by can_modify().
99 *
100 * @return callable Restores the unfiltered behaviour.
101 */
102 public static function allow_target_roles( int $target_id ): callable {
103 $target = get_user_by( 'id', $target_id );
104 $roles = $target ? array_values( (array) $target->roles ) : array();
105
106 if ( empty( $roles ) ) {
107 return static function (): void {};
108 }
109
110 $filter = static function ( $allowed ) use ( $roles ) {
111 return array_values( array_unique( array_merge( (array) $allowed, $roles ) ) );
112 };
113
114 add_filter( 'woocommerce_shop_manager_editable_roles', $filter );
115
116 return static function () use ( $filter ): void {
117 remove_filter( 'woocommerce_shop_manager_editable_roles', $filter );
118 };
119 }
120
121 /**
122 * Build the staff account permission error.
123 *
124 * @return \WP_Error
125 */
126 public static function denial(): \WP_Error {
127 return new \WP_Error(
128 'woocommerce_pos_rest_cannot_edit_staff_account',
129 __( 'Only an administrator can edit or delete a staff account from the POS.', 'woocommerce-pos' ),
130 array( 'status' => rest_authorization_required_code() )
131 );
132 }
133 }
134