PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.17.0
GiveWP – Donation Plugin and Fundraising Platform v4.17.0
4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 All 256 releases
← All changes | src/Framework/PaymentGateways/Routes/RouteSignature.php +118 -4 4.16.7 → 4.17.0 View file →
@@ -1,8 +1,9 @@
1 1 <?php
2 2
3 3 namespace Give\Framework\PaymentGateways\Routes;
4 4
5 +use Give\Framework\PaymentGateways\DataTransferObjects\GatewayRouteData;
5 6 use Give\Framework\Shims\Shim;
6 7
7 8 /**
8 9 * Route signature for creating secure gateway route methods
@@ -18,8 +19,15 @@
18 19 /**
19 20 * @var string
20 21 */
21 22 public $expiration;
23 + /**
24 + * Keys of the query args covered by this signature, sorted.
25 + *
26 + * @since 4.16.8
27 + * @var string[]
28 + */
29 + public $argKeys;
22 30
23 31 /**
24 32 * @since 2.19.5 replace wp_create_nonce with wp_hash and timestamp expiration
25 33 * @since 2.19.4 replace RouteSignature args with unique donationId
@@ -24,32 +32,138 @@
24 32 * @since 2.19.5 replace wp_create_nonce with wp_hash and timestamp expiration
25 33 * @since 2.19.4 replace RouteSignature args with unique donationId
26 34 * @since 2.19.0
27 35 *
36 + * @since 4.16.8 add $args so the route's own query args are covered
37 + *
28 38 * @param int $gatewayId
29 39 * @param string $gatewayMethod
30 40 * @param int $donationId
31 41 * @param string $expiration
42 + * @param array $args Query args the route carries, which the signature then covers. Values the
43 + * URL cannot carry — false, and null or empty arrays at any depth — are dropped.
32 44 */
33 - public function __construct($gatewayId, $gatewayMethod, $donationId, $expiration = null)
45 + public function __construct($gatewayId, $gatewayMethod, $donationId, $expiration = null, array $args = [])
34 46 {
47 + // add_query_arg leaves a false arg off the URL entirely, but only at the top level; a nested
48 + // false is serialized as '0' by both query builders and round-trips.
49 + $args = array_filter($args, static function ($value) {
50 + return $value !== false;
51 + });
52 +
53 + // Null and empty array values produce no query parameter at any depth, so signing them would
54 + // produce a signature the request coming back could never rebuild.
55 + $args = self::pruneArgs($args);
56 +
57 + ksort($args);
58 +
59 + $this->argKeys = array_keys($args);
35 60 $this->expiration = $expiration ?: $this->createExpirationTimestamp();
36 - $this->signature = $this->generateSignatureString($gatewayId, $gatewayMethod, $donationId, $this->expiration);
61 + $this->signature = $this->generateSignatureString(
62 + $gatewayId,
63 + $gatewayMethod,
64 + $donationId,
65 + $this->expiration,
66 + $args
67 + );
37 68 }
38 69
39 70
40 71 /**
72 + * Rebuilds the signature a request claims to carry, from the args that request was signed with.
73 + *
74 + * Args the gateway appended on the way back are not among them, so they are ignored; one that was
75 + * signed and has since been edited or dropped changes the hash.
76 + *
77 + * @since 4.16.8
78 + */
79 + public static function fromRouteData(GatewayRouteData $data): self
80 + {
81 + return new self(
82 + $data->gatewayId,
83 + $data->gatewayMethod,
84 + $data->routeSignatureId,
85 + $data->routeSignatureExpiration,
86 + array_intersect_key($data->queryParams, array_flip($data->routeSignatureArgKeys))
87 + );
88 + }
89 +
90 + /**
91 + * Drops null and empty array values at any depth, children first, so a parent left holding
92 + * nothing goes with them.
93 + *
94 + * @since 4.16.8
95 + */
96 + private static function pruneArgs(array $args): array
97 + {
98 + $args = array_map(static function ($value) {
99 + return is_array($value) ? self::pruneArgs($value) : $value;
100 + }, $args);
101 +
102 + return array_filter($args, static function ($value) {
103 + return $value !== null && $value !== [];
104 + });
105 + }
106 +
107 + /**
108 + * Normalizes args to what the request coming back will carry: PHP urldecodes each query value
109 + * once (%XX to its character, + to a space), and GatewayRoute then runs the request through
110 + * give_clean(). Signing the raw values instead rejects a genuine return whenever a value
111 + * changes shape in transit — a rawurlencoded return URL being the everyday case.
112 + *
113 + * Only URL generation runs this; values arriving on a request have been through the real thing.
114 + *
115 + * @since 4.16.8 normalize nested values too, as PHP decodes and give_clean() cleans at every depth
116 + * @since 4.16.8
117 + */
118 + public static function normalizeArgs(array $args): array
119 + {
120 + return array_map(static function ($value) {
121 + if (is_array($value)) {
122 + return self::normalizeArgs($value);
123 + }
124 +
125 + return is_string($value) ? give_clean(urldecode($value)) : $value;
126 + }, $args);
127 + }
128 +
129 + /**
130 + * Encodes normalized args for the URL. add_query_arg() writes values as given, so a raw value
131 + * holding an ampersand — a legacy form's success URL under plain permalinks — is split into
132 + * separate parameters on the way back and the signed value never round-trips. Encoding what was
133 + * signed means PHP's single decode hands the route exactly that value, however the gateway
134 + * shaped it.
135 + *
136 + * @since 4.16.8
137 + */
138 + public static function encodeArgs(array $args): array
139 + {
140 + return array_map(static function ($value) {
141 + if (is_array($value)) {
142 + return self::encodeArgs($value);
143 + }
144 +
145 + return is_string($value) ? rawurlencode($value) : $value;
146 + }, $args);
147 + }
148 +
149 + /**
41 150 * @since 2.19.5
42 151 *
43 152 * @param string $gatewayId
44 153 * @param string $gatewayMethod
45 154 * @param int $donationId
155 + * @since 4.16.8 append the route's query args
156 + *
46 157 * @param string $expiration
47 158 * @return string
48 159 */
49 - private function generateSignatureString($gatewayId, $gatewayMethod, $donationId, $expiration)
160 + private function generateSignatureString($gatewayId, $gatewayMethod, $donationId, $expiration, array $args = [])
50 161 {
51 - return "$gatewayId@$gatewayMethod:$donationId|$expiration";
162 + $signature = "$gatewayId@$gatewayMethod:$donationId|$expiration";
163 +
164 + // A signature made before args were covered has none, and has to keep hashing to what it did then.
165 + return $args ? $signature . '|' . http_build_query($args) : $signature;
52 166 }
53 167
54 168 /**
55 169 * @since 2.19.0