PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.21
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.21
1.10.23 1.10.22 1.10.21 1.10.20 1.10.19 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 All 167 releases
← All changes | includes/Services/Session_Registry.php +39 -20 1.10.20 → 1.10.21 View file →
@@ -96,11 +96,9 @@
96 96 *
97 97 * @return array
98 98 */
99 99 public function entries( int $user_id ): array {
100 - $entries = get_user_meta( $user_id, self::META_KEY, true );
101 -
102 - return \is_array( $entries ) ? $entries : array();
100 + return $this->read_row( $user_id );
103 101 }
104 102
105 103 /**
106 104 * Read one stored session.
@@ -128,12 +126,9 @@
128 126 // BEFORE the read: a pre-cap row can be too large to load, and this is the first
129 127 // point in the login flow where WCPOS knows the user id.
130 128 $this->discard_oversized_row( $user_id );
131 129
132 - $refresh_tokens = get_user_meta( $user_id, self::META_KEY, true );
133 - if ( ! \is_array( $refresh_tokens ) ) {
134 - $refresh_tokens = array();
135 - }
130 + $refresh_tokens = $this->read_row( $user_id );
136 131
137 132 // Clean up expired tokens.
138 133 $refresh_tokens = array_filter(
139 134 $refresh_tokens,
@@ -205,10 +200,10 @@
205 200 *
206 201 * @return array
207 202 */
208 203 public function list( int $user_id ): array {
209 - $refresh_tokens = get_user_meta( $user_id, self::META_KEY, true );
210 - if ( ! \is_array( $refresh_tokens ) ) {
204 + $refresh_tokens = $this->read_row( $user_id );
205 + if ( empty( $refresh_tokens ) ) {
211 206 return array();
212 207 }
213 208
214 209 $sessions = array();
@@ -250,14 +245,12 @@
250 245 *
251 246 * @return bool
252 247 */
253 248 public function is_live( int $user_id, string $jti ): bool {
254 - $refresh_tokens = get_user_meta( $user_id, self::META_KEY, true );
255 - if ( ! \is_array( $refresh_tokens ) ) {
256 - return false;
257 - }
249 + $row = $this->read_row( $user_id );
258 250
259 - return isset( $refresh_tokens[ $jti ] ) && $refresh_tokens[ $jti ]['expires'] > time();
251 + // read_row() has already skipped malformed entries, which are not live sessions.
252 + return isset( $row[ $jti ] ) && (int) $row[ $jti ]['expires'] > time();
260 253 }
261 254
262 255 /**
263 256 * Refresh a session's `last_active`, at most once every few minutes.
@@ -277,10 +270,11 @@
277 270
278 271 $key = self::SESSION_SEEN_TRANSIENT_PREFIX . $jti;
279 272 $seen = get_transient( $key );
280 273
281 - // The throttle reads the transient, never the session row: this runs on every
282 - // authenticated request, and the row is the one thing this path must not touch.
274 + // The throttle keeps activity in the transient, out of the session row: this runs
275 + // on every authenticated request, and a per-request WRITE to the row would race
276 + // logins and revokes.
283 277 if ( is_numeric( $seen ) && time() - (int) $seen < self::SESSION_ACTIVITY_REFRESH_SECONDS ) {
284 278 return;
285 279 }
286 280
@@ -299,10 +293,10 @@
299 293 public function refresh_activity( int $user_id, string $jti ): bool {
300 294 // Public surface: any caller reaching the row goes through the size guard first.
301 295 $this->discard_oversized_row( $user_id );
302 296
303 - $refresh_tokens = get_user_meta( $user_id, self::META_KEY, true );
304 - if ( ! \is_array( $refresh_tokens ) || ! isset( $refresh_tokens[ $jti ] ) ) {
297 + $refresh_tokens = $this->read_row( $user_id );
298 + if ( ! isset( $refresh_tokens[ $jti ] ) ) {
305 299 return false;
306 300 }
307 301
308 302 $refresh_tokens[ $jti ]['last_active'] = time();
@@ -323,10 +317,10 @@
323 317 if ( empty( $refresh_jti ) || $access_expires <= 0 ) {
324 318 return false;
325 319 }
326 320
327 - $refresh_tokens = get_user_meta( $user_id, self::META_KEY, true );
328 - if ( ! \is_array( $refresh_tokens ) || ! isset( $refresh_tokens[ $refresh_jti ] ) ) {
321 + $refresh_tokens = $this->read_row( $user_id );
322 + if ( ! isset( $refresh_tokens[ $refresh_jti ] ) ) {
329 323 return false;
330 324 }
331 325
332 326 $current_access_expires = isset( $refresh_tokens[ $refresh_jti ]['access_expires'] ) ? (int) $refresh_tokens[ $refresh_jti ]['access_expires'] : 0;
@@ -361,8 +355,33 @@
361 355 return true;
362 356 }
363 357
364 358 return false;
359 + }
360 +
361 + /**
362 + * Read the stored sessions, skipping malformed entries.
363 + *
364 + * An entry that is not an array or has no expiry is not a session, so it is left out
365 + * and the valid entries keep their keys. This never writes: a path that already saves
366 + * the row saves it without the skipped entries, and a pure read leaves the row alone.
367 + *
368 + * @param int $user_id The user ID.
369 + *
370 + * @return array Valid session entries keyed by refresh token JTI.
371 + */
372 + private function read_row( int $user_id ): array {
373 + $row = get_user_meta( $user_id, self::META_KEY, true );
374 + if ( ! \is_array( $row ) ) {
375 + return array();
376 + }
377 +
378 + return array_filter(
379 + $row,
380 + function ( $entry ) {
381 + return \is_array( $entry ) && isset( $entry['expires'] );
382 + }
383 + );
365 384 }
366 385
367 386 /**
368 387 * Drop the stored session row when it is too large to be read safely.