Changeset 3740915
- Timestamp:
- 10/12/2026 09:05:05 AM (13 days ago)
- Location:
- klarna-payments-for-woocommerce
- Files:
-
- 12 added
- 48 edited
- 1 copied
-
tags/4.13.4 (copied) (copied from klarna-payments-for-woocommerce/trunk )
-
tags/4.13.4/changelog.txt (modified) (1 diff)
-
tags/4.13.4/classes/class-kp-ajax.php (modified) (1 diff)
-
tags/4.13.4/classes/class-kp-assets.php (modified) (4 diffs)
-
tags/4.13.4/classes/class-kp-callbacks.php (modified) (1 diff)
-
tags/4.13.4/classes/class-kp-logger.php (modified) (3 diffs)
-
tags/4.13.4/classes/requests/class-kp-requests.php (modified) (3 diffs)
-
tags/4.13.4/classes/requests/post/class-kp-unavailable-features.php (modified) (1 diff)
-
tags/4.13.4/dependencies/autoload.php (modified) (1 diff)
-
tags/4.13.4/dependencies/composer/autoload_classmap.php (modified) (1 diff)
-
tags/4.13.4/dependencies/composer/autoload_real.php (modified) (2 diffs)
-
tags/4.13.4/dependencies/composer/autoload_static.php (modified) (3 diffs)
-
tags/4.13.4/dependencies/composer/installed.json (modified) (3 diffs)
-
tags/4.13.4/dependencies/composer/installed.php (modified) (1 diff)
-
tags/4.13.4/dependencies/krokedil/wp-api/CHANGELOG.md (modified) (1 diff)
-
tags/4.13.4/dependencies/krokedil/wp-api/src/FieldMasker.php (added)
-
tags/4.13.4/dependencies/krokedil/wp-api/src/KeyMasker.php (added)
-
tags/4.13.4/dependencies/krokedil/wp-api/src/Logger.php (modified) (9 diffs)
-
tags/4.13.4/dependencies/krokedil/wp-api/src/Masking.php (added)
-
tags/4.13.4/dependencies/krokedil/wp-api/src/Request.php (modified) (7 diffs)
-
tags/4.13.4/klarna-payments-for-woocommerce.php (modified) (4 diffs)
-
tags/4.13.4/languages/klarna-payments-for-woocommerce.pot (modified) (8 diffs)
-
tags/4.13.4/readme.txt (modified) (2 diffs)
-
tags/4.13.4/src/ExpressCheckout/KECOneStepIntegration.php (modified) (1 diff)
-
tags/4.13.4/src/Logging (added)
-
tags/4.13.4/src/Logging/LogMasking.php (added)
-
tags/4.13.4/src/Logging/LogWriter.php (added)
-
tags/4.13.4/src/OrderManagement/Request/Request.php (modified) (2 diffs)
-
tags/4.13.4/vendor/composer/autoload_classmap.php (modified) (1 diff)
-
tags/4.13.4/vendor/composer/autoload_static.php (modified) (1 diff)
-
tags/4.13.4/vendor/composer/installed.php (modified) (2 diffs)
-
trunk/changelog.txt (modified) (1 diff)
-
trunk/classes/class-kp-ajax.php (modified) (1 diff)
-
trunk/classes/class-kp-assets.php (modified) (4 diffs)
-
trunk/classes/class-kp-callbacks.php (modified) (1 diff)
-
trunk/classes/class-kp-logger.php (modified) (3 diffs)
-
trunk/classes/requests/class-kp-requests.php (modified) (3 diffs)
-
trunk/classes/requests/post/class-kp-unavailable-features.php (modified) (1 diff)
-
trunk/dependencies/autoload.php (modified) (1 diff)
-
trunk/dependencies/composer/autoload_classmap.php (modified) (1 diff)
-
trunk/dependencies/composer/autoload_real.php (modified) (2 diffs)
-
trunk/dependencies/composer/autoload_static.php (modified) (3 diffs)
-
trunk/dependencies/composer/installed.json (modified) (3 diffs)
-
trunk/dependencies/composer/installed.php (modified) (1 diff)
-
trunk/dependencies/krokedil/wp-api/CHANGELOG.md (modified) (1 diff)
-
trunk/dependencies/krokedil/wp-api/src/FieldMasker.php (added)
-
trunk/dependencies/krokedil/wp-api/src/KeyMasker.php (added)
-
trunk/dependencies/krokedil/wp-api/src/Logger.php (modified) (9 diffs)
-
trunk/dependencies/krokedil/wp-api/src/Masking.php (added)
-
trunk/dependencies/krokedil/wp-api/src/Request.php (modified) (7 diffs)
-
trunk/klarna-payments-for-woocommerce.php (modified) (4 diffs)
-
trunk/languages/klarna-payments-for-woocommerce.pot (modified) (8 diffs)
-
trunk/readme.txt (modified) (2 diffs)
-
trunk/src/ExpressCheckout/KECOneStepIntegration.php (modified) (1 diff)
-
trunk/src/Logging (added)
-
trunk/src/Logging/LogMasking.php (added)
-
trunk/src/Logging/LogWriter.php (added)
-
trunk/src/OrderManagement/Request/Request.php (modified) (2 diffs)
-
trunk/vendor/composer/autoload_classmap.php (modified) (1 diff)
-
trunk/vendor/composer/autoload_static.php (modified) (1 diff)
-
trunk/vendor/composer/installed.php (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
-
klarna-payments-for-woocommerce/tags/4.13.4/changelog.txt
r3728366 r3740915 1 1 *** Klarna for WooCommerce Changelog *** 2 2026-10-12 - version 4.13.4 3 * Enhancement - Masked customer personal, and other sensitive data and credentials in the plugin log. 4 * Fix - Order details are now only added to the pay for order page when WooCommerce would show the payment form: the request carries the order's key, the customer may pay for the order, the order still needs payment, and a guest has confirmed the billing email. 5 2 6 2026-10-05 - version 4.13.3 3 7 * Enhancement - Improved handling of Klarna authorization callbacks and background processing. -
klarna-payments-for-woocommerce/tags/4.13.4/classes/class-kp-ajax.php
r3728366 r3740915 100 100 wp_send_json_success( $order->get_checkout_order_received_url() ); 101 101 } else { 102 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Auth token: %s. %s', $order->get_id(), $auth_token, $recurring_token->get_error_message() ) );102 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Failed to create a recurring token: %s', $order->get_id(), $recurring_token->get_error_message() ) ); 103 103 wp_send_json_error( 'customer_token_failed' ); 104 104 } -
klarna-payments-for-woocommerce/tags/4.13.4/classes/class-kp-assets.php
r3716655 r3740915 6 6 */ 7 7 8 use Automattic\WooCommerce\Internal\Utilities\Users; 8 9 use Krokedil\Klarna\Features; 9 10 use Krokedil\Klarna\PluginFeatures; … … 188 189 189 190 /** 191 * Whether WooCommerce asks the guest to confirm the billing email before showing the order-pay form. 192 * 193 * @param WC_Order $order The order being paid for. 194 * @return bool 195 */ 196 private function guest_should_verify_email( $order ) { 197 // WooCommerce versions without the check never ask for verification. 198 if ( ! is_callable( array( Users::class, 'should_user_verify_order_email' ) ) ) { 199 return false; 200 } 201 202 // Mirrors WC_Shortcode_Checkout::guest_should_verify_email(), which accepts the email posted by its verify form. 203 $supplied_email = null; 204 if ( wp_verify_nonce( (string) filter_input( INPUT_POST, 'check_submission' ), 'wc_verify_email' ) ) { 205 $supplied_email = sanitize_email( wp_unslash( (string) filter_input( INPUT_POST, 'email' ) ) ); 206 } 207 208 return Users::should_user_verify_order_email( $order->get_id(), $supplied_email, 'order-pay' ); 209 } 210 211 /** 190 212 * Get the params for the Klarna Payments checkout script. 191 213 * … … 200 222 201 223 if ( ! empty( $order_id ) ) { 202 $order = wc_get_order( $order_id ); 203 if ( ! empty( $order ) ) { 224 $order = wc_get_order( $order_id ); 225 $request_key = (string) filter_input( INPUT_GET, 'key', FILTER_SANITIZE_SPECIAL_CHARS ); 226 227 // Only expose the order when WooCommerce would render the pay form, so the same checks in the same order. 228 if ( 229 ! empty( $order ) 230 && hash_equals( $order->get_order_key(), $request_key ) 231 && current_user_can( 'pay_for_order', $order_id ) 232 && $order->needs_payment() 233 && ! $this->guest_should_verify_email( $order ) 234 ) { 204 235 $order_key = $order->get_order_key(); 236 } else { 237 $order_id = null; 205 238 } 206 239 } … … 240 273 'order_key' => $order_key, 241 274 'submit_button_selectors' => array( 'input#place_order', 'button#place_order' ), 242 'addresses' => $pay_for_order? array(275 'addresses' => null !== $order_key ? array( 243 276 'billing' => $customer['billing'], 244 277 'shipping' => $customer['shipping'], -
klarna-payments-for-woocommerce/tags/4.13.4/classes/class-kp-callbacks.php
r3728366 r3740915 241 241 $order->add_order_note( __( 'Failed to create a recurring token when returning from the hosted payment page.', 'klarna-payments-for-woocommerce' ) . $recurring_token->get_error_message() ); 242 242 243 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Auth token: %s. %s', $order->get_id(), $auth_token, $recurring_token->get_error_message() ) );243 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Failed to create a recurring token: %s', $order->get_id(), $recurring_token->get_error_message() ) ); 244 244 } 245 245 -
klarna-payments-for-woocommerce/tags/4.13.4/classes/class-kp-logger.php
r3716655 r3740915 9 9 exit; // Exit if accessed directly. 10 10 } 11 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 11 13 12 14 /** … … 28 30 public static function log( $data ) { 29 31 $kp_settings = get_option( 'woocommerce_klarna_payments_settings', array() ); 30 if ( 'no' !== ( $kp_settings['logging'] ?? 'no' ) ) { 31 $message = self::format_data( $data ); 32 KP_WC()->logger()->info( wp_json_encode( $message ) ); 32 if ( 'no' === ( $kp_settings['logging'] ?? 'no' ) ) { 33 return; 33 34 } 35 36 $message = self::format_data( $data ); 37 38 // A failure here costs the entry, it never lets an unmasked one through. 39 try { 40 $message = KeyMasker::mask( self::format_data( $data ) ); 41 } catch ( \Throwable $e ) { 42 $message = array( 'error' => KeyMasker::FAILED ); 43 } 44 45 KP_WC()->logger()->info( wp_json_encode( $message ) ); 34 46 } 35 47 … … 41 53 */ 42 54 public static function format_data( $data ) { 43 if ( isset( $data['request']['body'] ) ) {55 if ( isset( $data['request']['body'] ) && is_string( $data['request']['body'] ) ) { 44 56 $request_body = json_decode( $data['request']['body'], true ); 45 57 $data['request']['body'] = $request_body; -
klarna-payments-for-woocommerce/tags/4.13.4/classes/requests/class-kp-requests.php
r3664907 r3740915 9 9 10 10 use Krokedil\Klarna\Utilities\ApiCredentialsUtility; 11 use Krokedil\Klarna\Logging\LogMasking; 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 11 13 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Request; 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Logger;13 14 14 15 /** … … 87 88 ); 88 89 89 parent::__construct( $config, $settings, $arguments );90 parent::__construct( $config, $settings, $arguments, self::get_masked_fields() ); 90 91 $this->set_credentials(); 91 92 $this->iframe_options = new KP_IFrame( $settings ); … … 285 286 286 287 /** 287 * Logs the response from the request. 288 * 289 * @param array|\WP_Error $response The response from the request. 290 * @param array $request_args The request args. 291 * @param string $request_url The request URL. 292 * 293 * @return void 294 */ 295 protected function log_response( $response, $request_args, $request_url ) { 296 $code = wp_remote_retrieve_response_code( $response ); 297 298 // Get the response body if its not a WP_Error. 299 $response_body = is_wp_error( $response ) ? array() : json_decode( wp_remote_retrieve_body( $response ), true ); 300 301 // Parse the Request body into an array if its json format. 302 $request_body = $request_args['body'] ?? ''; 303 $decoded_body = json_decode( $request_body ); 304 $request_args['body'] = $decoded_body ?? $request_args['body'] ?? null; 305 $request_args = $this->sanitize_request_args( $request_args ); 306 307 $arguments = $this->arguments; 308 if ( isset( $arguments['username'] ) ) { 309 $arguments['username'] = '[REDACTED]'; 310 } 311 if ( isset( $arguments['password'] ) ) { 312 $arguments['password'] = '[REDACTED]'; 313 } 314 315 KP_WC()->logger()->info( 316 wp_json_encode( 317 array( 318 'type' => $this->method, 319 'title' => $this->log_title, 320 'arguments' => $arguments, 321 'request' => $request_args, 322 'request_url' => $request_url, 323 'response' => array( 324 'body' => $response_body, 325 'code' => $code, 326 ), 327 'timestamp' => date( 'Y-m-d H:i:s' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display. 328 'stack' => Logger::get_stack( $this->config['extended_debugging'] ), 329 'plugin_version' => $this->config['plugin_version'], 330 ) 331 ) 288 * The fields masked out of the log, merged into what the package already masks. 289 * 290 * @return array 291 */ 292 protected static function get_masked_fields() { 293 return array( 294 'request' => LogMasking::request_fields(), 295 'response' => LogMasking::response_fields(), 296 'arguments' => array( 'api_password', 'auth_token', 'recurring_token' ), 297 ); 298 } 299 300 /** 301 * Mask the tokens Klarna addresses a resource by in the path. A session or order id is 302 * left readable, since neither authorises anything and both are what a log is searched by. 303 * 304 * @param string $request_url The request URL. 305 * @return string 306 */ 307 protected function mask_request_url( $request_url ) { 308 return preg_replace( 309 array( '#/authorizations/[^/?]+#', '#/tokens/[^/?]+#' ), 310 array( '/authorizations/' . KeyMasker::REDACTED, '/tokens/' . KeyMasker::REDACTED ), 311 $request_url 332 312 ); 333 313 } -
klarna-payments-for-woocommerce/tags/4.13.4/classes/requests/post/class-kp-unavailable-features.php
r3664907 r3740915 127 127 return new WP_Error( $code, $error_message ); 128 128 } 129 130 /**131 * Logs the response from the request.132 *133 * @param array|\WP_Error $response The response from the request.134 * @param array $request_args The request args.135 * @param string $request_url The request URL.136 * @return void137 */138 protected function log_response( $response, $request_args, $request_url ) {139 $this->arguments['api_password'] = '[REDACTED]';140 parent::log_response( $response, $request_args, $request_url );141 }142 129 } -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/autoload.php
r3728366 r3740915 20 20 require_once __DIR__ . '/composer/autoload_real.php'; 21 21 22 return ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e3::getLoader();22 return ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386::getLoader(); -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/composer/autoload_classmap.php
r3728366 r3740915 58 58 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\StoreApi\\StoreApiLineShipping' => $vendorDir . '/krokedil/woocommerce/src/StoreApi/StoreApiLineShipping.php', 59 59 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\Subscription' => $vendorDir . '/krokedil/woocommerce/src/Subscription.php', 60 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\FieldMasker' => $vendorDir . '/krokedil/wp-api/src/FieldMasker.php', 61 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\KeyMasker' => $vendorDir . '/krokedil/wp-api/src/KeyMasker.php', 60 62 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Logger' => $vendorDir . '/krokedil/wp-api/src/Logger.php', 63 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Masking' => $vendorDir . '/krokedil/wp-api/src/Masking.php', 61 64 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Request' => $vendorDir . '/krokedil/wp-api/src/Request.php', 62 65 ); -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/composer/autoload_real.php
r3728366 r3740915 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e35 class ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386 6 6 { 7 7 private static $loader; … … 23 23 } 24 24 25 spl_autoload_register(array('ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e3', 'loadClassLoader'), true, true);25 spl_autoload_register(array('ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386', 'loadClassLoader'), true, true); 26 26 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); 27 spl_autoload_unregister(array('ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e3', 'loadClassLoader'));27 spl_autoload_unregister(array('ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386', 'loadClassLoader')); 28 28 29 29 require __DIR__ . '/autoload_static.php'; 30 call_user_func(\Composer\Autoload\ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::getInitializer($loader));30 call_user_func(\Composer\Autoload\ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::getInitializer($loader)); 31 31 32 32 $loader->register(true); -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/composer/autoload_static.php
r3728366 r3740915 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit c3911f91ac7a4070427b96bb312df1e37 class ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 88 88 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\StoreApi\\StoreApiLineShipping' => __DIR__ . '/..' . '/krokedil/woocommerce/src/StoreApi/StoreApiLineShipping.php', 89 89 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\Subscription' => __DIR__ . '/..' . '/krokedil/woocommerce/src/Subscription.php', 90 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\FieldMasker' => __DIR__ . '/..' . '/krokedil/wp-api/src/FieldMasker.php', 91 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\KeyMasker' => __DIR__ . '/..' . '/krokedil/wp-api/src/KeyMasker.php', 90 92 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Logger' => __DIR__ . '/..' . '/krokedil/wp-api/src/Logger.php', 93 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Masking' => __DIR__ . '/..' . '/krokedil/wp-api/src/Masking.php', 91 94 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Request' => __DIR__ . '/..' . '/krokedil/wp-api/src/Request.php', 92 95 ); … … 95 98 { 96 99 return \Closure::bind(function () use ($loader) { 97 $loader->prefixLengthsPsr4 = ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::$prefixLengthsPsr4;98 $loader->prefixDirsPsr4 = ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::$prefixDirsPsr4;99 $loader->classMap = ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::$classMap;100 $loader->prefixLengthsPsr4 = ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::$prefixLengthsPsr4; 101 $loader->prefixDirsPsr4 = ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::$prefixDirsPsr4; 102 $loader->classMap = ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::$classMap; 100 103 101 104 }, null, ClassLoader::class); -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/composer/installed.json
r3728366 r3740915 199 199 { 200 200 "name": "krokedil\/wp-api", 201 "version": "1. 1.1",202 "version_normalized": "1. 1.1.0",201 "version": "1.2.0", 202 "version_normalized": "1.2.0.0", 203 203 "source": { 204 204 "type": "git", 205 205 "url": "https:\/\/github.com\/krokedil\/wp-api.git", 206 "reference": "a3ba33f7ac640456dad36a33d711d20091c71aea" 207 }, 208 "dist": { 209 "type": "zip", 210 "url": "https:\/\/api.github.com\/repos\/krokedil\/wp-api\/zipball\/a3ba33f7ac640456dad36a33d711d20091c71aea", 211 "reference": "a3ba33f7ac640456dad36a33d711d20091c71aea", 212 "shasum": "" 213 }, 214 "require-dev": { 206 "reference": "3473dd2fb6b31ed5060ecd69877c98b5287ebe48" 207 }, 208 "dist": { 209 "type": "zip", 210 "url": "https:\/\/api.github.com\/repos\/krokedil\/wp-api\/zipball\/3473dd2fb6b31ed5060ecd69877c98b5287ebe48", 211 "reference": "3473dd2fb6b31ed5060ecd69877c98b5287ebe48", 212 "shasum": "" 213 }, 214 "require-dev": { 215 "dealerdirect\/phpcodesniffer-composer-installer": "^1.0", 215 216 "php": "~7.1 || ~8.0", 216 217 "php-stubs\/woocommerce-stubs": "^7.2", 217 "php-stubs\/wordpress-stubs": "^6.1" 218 }, 219 "time": "2024-04-22T09:03:39+00:00", 218 "php-stubs\/wordpress-stubs": "^6.1", 219 "phpstan\/phpstan": "^2.0", 220 "phpunit\/phpunit": "^9.6", 221 "squizlabs\/php_codesniffer": "^3.13", 222 "szepeviktor\/phpstan-wordpress": "^2.0", 223 "wp-coding-standards\/wpcs": "^3.1" 224 }, 225 "time": "2026-10-07T08:46:15+00:00", 220 226 "type": "library", 221 227 "installation-source": "dist", … … 225 231 } 226 232 }, 233 "scripts": { 234 "test": [ 235 "phpunit" 236 ], 237 "phpcs": [ 238 "phpcs" 239 ], 240 "phpcbf": [ 241 "phpcbf" 242 ], 243 "phpstan": [ 244 "phpstan analyse --memory-limit=1G" 245 ] 246 }, 227 247 "license": [ 228 248 "GPL-V2" … … 236 256 "description": "A composer library for Krokedil WordPress plugins that contains classes for working with wp_remote_request to setup request classes. Also uses the WooCommerce logger to log request reponses.", 237 257 "support": { 238 "source": "https:\/\/github.com\/krokedil\/wp-api\/tree\/1. 1.1",258 "source": "https:\/\/github.com\/krokedil\/wp-api\/tree\/1.2.0", 239 259 "issues": "https:\/\/github.com\/krokedil\/wp-api\/issues" 240 260 }, -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/composer/installed.php
r3728366 r3740915 3 3 namespace KrokedilKlarnaPaymentsDeps; 4 4 5 return array('root' => array('name' => '__root__', 'pretty_version' => '4.13. 3', 'version' => '4.13.3.0', 'reference' => '0d6c8537e224d077da2c9b85736448423cc02baf', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \true), 'versions' => array('__root__' => array('pretty_version' => '4.13.3', 'version' => '4.13.3.0', 'reference' => '0d6c8537e224d077da2c9b85736448423cc02baf', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/settings-page' => array('pretty_version' => '1.3.7', 'version' => '1.3.7.0', 'reference' => 'ef40906dd574b8c61d5831d247661a909c0463e6', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/settings-page', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/support' => array('pretty_version' => '1.0.3', 'version' => '1.0.3.0', 'reference' => 'e4ee49d1c862e45fb284247c84d7c1f5583cb6ba', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/support', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/woocommerce' => array('pretty_version' => '1.9.2', 'version' => '1.9.2.0', 'reference' => '6c38cf569df5eeb3598f206d449eca86ae9ac402', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/woocommerce', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/wp-api' => array('pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => 'a3ba33f7ac640456dad36a33d711d20091c71aea', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/wp-api', 'aliases' => array(), 'dev_requirement' => \false)));5 return array('root' => array('name' => '__root__', 'pretty_version' => '4.13.4', 'version' => '4.13.4.0', 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \true), 'versions' => array('__root__' => array('pretty_version' => '4.13.4', 'version' => '4.13.4.0', 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/settings-page' => array('pretty_version' => '1.3.7', 'version' => '1.3.7.0', 'reference' => 'ef40906dd574b8c61d5831d247661a909c0463e6', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/settings-page', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/support' => array('pretty_version' => '1.0.3', 'version' => '1.0.3.0', 'reference' => 'e4ee49d1c862e45fb284247c84d7c1f5583cb6ba', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/support', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/woocommerce' => array('pretty_version' => '1.9.2', 'version' => '1.9.2.0', 'reference' => '6c38cf569df5eeb3598f206d449eca86ae9ac402', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/woocommerce', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/wp-api' => array('pretty_version' => '1.2.0', 'version' => '1.2.0.0', 'reference' => '3473dd2fb6b31ed5060ecd69877c98b5287ebe48', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/wp-api', 'aliases' => array(), 'dev_requirement' => \false))); -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/krokedil/wp-api/CHANGELOG.md
r3150014 r3740915 7 7 8 8 ## [Unreleased] 9 10 ------------------ 11 ## [1.2.0] - 2026-10-07 12 ### Added 13 * Added configurable masking for the request, the response and the request arguments, replacing the hardcoded Authorization header and `username`/`password` handling. Rules are declared as `$request_fields_to_mask`, `$response_fields_to_mask` and `$argument_fields_to_mask`, or passed as the `$masked_fields` constructor argument, where they merge into what the class already declares. 14 * Added an allow list to the masking configuration. The reserved `keep` key masks every key in a container that it does not name, so a field the provider adds later is masked by default. 15 * Added `Krokedil\WpApi\FieldMasker`, so a plugin that logs from outside a `Request` subclass can apply the same configured rules. 16 * Added `mask_request_url()` for APIs that address a resource by a token in the path. It returns the URL unchanged by default, and an override that throws costs the log line and not the API call. 17 * Added PHPUnit, PHPCS and PHPStan with `composer test`, `composer phpcs` and `composer phpstan`, and GitHub Actions workflows for pull requests. 18 * Added XML support for response bodies. `get_response_body()` parses the body by its content type: JSON is decoded, XML is parsed into an array, HTML is returned as a string. A body with no or an unknown content type is decoded as JSON when it is valid JSON, so the response masking rules still reach into it. 19 * Added log levels. `Logger::log()` writes with the WooCommerce logger at the `log_level` of the entry, `error` for a non 2xx response and for a body `status` of `error`, `warning` for a body `status` of `warning`, otherwise `info`. 20 * `WP_Error` responses from `wp_remote_request()` are now logged. 21 * Added the `krokedil_wp_api_mask_log_data` filter. Return `false` to turn the log masking off while debugging. It gets the plugin slug as its second argument. There is deliberately no setting for it. 22 * Added `Krokedil\WpApi\Masking`, with `mask_fields()`, `mask_keys()` and `is_enabled()`, so a plugin that masks data outside a `Request` subclass honours the filter too. 23 24 ### Changed 25 * `[MISSING]` now tells an empty value from a sent one on every masked field, rather than only on the Authorization header. 26 * Masking no longer decides by length whether a header holds a token. Any value a rule names is replaced, whatever its length. 27 * Data nested deeper than twelve levels is masked rather than walked. 28 * `request()` returns the body parsed by its content type rather than always through `json_decode()`. A 2xx HTML body, or a body with no or an unknown content type that is not JSON, is now returned as a string where it used to be `null`, and a body labelled JSON or XML that cannot be parsed returns a `WP_Error`. 29 * Request logs are written at the `info` level rather than the `notice` default of `WC_Logger::add()`, so a store with its log threshold at `notice` or higher no longer records successful requests. 30 31 ### Deprecated 32 * `sanitize_request_args()` is deprecated and delegates to the configured masking, keeping its behaviour. 33 34 ### Security 35 * The response body is now masked. It was logged in full, so every field the provider returned, customer details included, reached the log. 36 * Stack trace arguments are now masked. With extended debugging on, every value passed to every caller was json encoded into the log as it was. Frame arguments are positional, so a bare token with no key name still survives unless its shape gives it away. 37 * Added a key name pass over the finished log entry. `Krokedil\WpApi\KeyMasker` masks by key name wherever it appears, and by shape for an Authorization value, a JWT and a standalone base64 blob, catching what no rule describes. Consuming plugins widen the list with `KeyMasker::add_keys()`. 38 * Masking fails closed. A section that cannot be masked is logged as `[MASKING FAILED]` rather than in the clear. 9 39 10 40 ------------------ -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/krokedil/wp-api/src/Logger.php
r3150014 r3740915 8 8 namespace KrokedilKlarnaPaymentsDeps\Krokedil\WpApi; 9 9 10 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 11 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Masking; 10 12 /** 11 13 * Logger class for the api requests. Used the WooCommerce logger. … … 14 16 { 15 17 /** 18 * The log levels the WooCommerce logger accepts. 19 */ 20 const LOG_LEVELS = array('emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug'); 21 /** 16 22 * WC Logger instance. 17 23 * 18 * @var \WC_Logger $log24 * @var \WC_Logger_Interface|null $log 19 25 */ 20 26 public static $log; … … 28 34 public static function log($slug, $log_data = array()) 29 35 { 30 // Log the data.31 36 if (empty(self::$log)) { 32 self::$log = new \WC_Logger();37 self::$log = wc_get_logger(); 33 38 } 34 self::$log->add($slug, wp_json_encode($log_data)); 39 // Read the level before masking, a failed pass replaces the whole entry. 40 $log_level = $log_data['log_level'] ?? 'info'; 41 if (!\in_array($log_level, self::LOG_LEVELS, \true)) { 42 $log_level = 'info'; 43 } 44 // A failure here costs the entry, it never lets an unmasked one through. 45 if (Masking::is_enabled($slug)) { 46 try { 47 $log_data = static::mask_log_data($log_data); 48 } catch (\Throwable $e) { 49 $log_data = array('error' => KeyMasker::FAILED); 50 } 51 } 52 self::$log->log($log_level, wp_json_encode($log_data), array('source' => $slug)); 53 } 54 /** 55 * The last pass over the finished entry, catching anything no rule described. 56 * 57 * @param array $log_data The data to log. 58 * @return mixed 59 */ 60 protected static function mask_log_data($log_data) 61 { 62 return KeyMasker::mask($log_data); 35 63 } 36 64 /** … … 38 66 * 39 67 * @param bool $extended_debugging Whether to include the arguments in the stack trace. This should never be turned on by default, but rather only used when extra information is needed. 68 * @param bool $mask Whether to mask the arguments in the stack trace. 40 69 * @return array 41 70 */ 42 public static function get_stack($extended_debugging = \false )71 public static function get_stack($extended_debugging = \false, $mask = \true) 43 72 { 44 73 $debug_data = \debug_backtrace(); … … 48 77 $count = \count($debug_data); 49 78 for ($i = 5; $i < $count; $i++) { 50 self::process_debug_line($stack, $debug_data[$i], $extended_debugging );79 self::process_debug_line($stack, $debug_data[$i], $extended_debugging, $mask); 51 80 } 52 81 return $stack; … … 58 87 * @param array $debug_line The debug info from the raw stack trace. 59 88 * @param bool $extended_debugging Whether to include the arguments in the stack trace. 89 * @param bool $mask Whether to mask the arguments. 60 90 * @return void 61 91 */ 62 private static function process_debug_line(&$stack, $debug_line, $extended_debugging )92 private static function process_debug_line(&$stack, $debug_line, $extended_debugging, $mask = \true) 63 93 { 64 94 $class = $debug_line['class'] ?? ''; … … 68 98 self::handle_wp_hook($class, $function, $args, $debug_line); 69 99 // Construct a caller string. 70 $caller = self::get_caller_string($class, $type, $function, $args, $extended_debugging );100 $caller = self::get_caller_string($class, $type, $function, $args, $extended_debugging, $mask); 71 101 $row = array('file' => $debug_line['file'] ?? '', 'line' => $debug_line['line'] ?? '', 'function' => $caller); 72 102 $stack[] = $row; 73 103 } 74 104 /** 75 * Get the caller string from the stack trace line. 105 * Get the caller string from the stack trace line. Argument values are masked first, but 106 * frame arguments are positional, so there is no key name to match on: a bare token passed 107 * as an argument survives unless its own shape gives it away. Treat these logs as sensitive. 76 108 * 77 109 * @param string $class The class name. … … 80 112 * @param array $args The arguments passed to the caller. 81 113 * @param bool $extended_debugging Whether to include the arguments in the stack trace. 114 * @param bool $mask Whether to mask the arguments. 82 115 * @return string 83 116 */ 84 private static function get_caller_string($class, $type, $function, $args, $extended_debugging )117 private static function get_caller_string($class, $type, $function, $args, $extended_debugging, $mask = \true) 85 118 { 86 119 // Construct a caller string. … … 88 121 $caller .= '('; 89 122 // Only add data if we are doing extended debugging. 90 $caller .= $extended_debugging ? \implode(', ', \array_map(function ($value) { 123 $caller .= $extended_debugging ? \implode(', ', \array_map(function ($value) use($mask) { 124 try { 125 $value = $mask ? KeyMasker::mask($value) : $value; 126 } catch (\Throwable $e) { 127 $value = KeyMasker::FAILED; 128 } 91 129 // Json encode all values so that we can see what objects and arrays are passed. Dont escape anything, partial output on errors, and ignore slashes and line terminators. 92 130 return wp_json_encode($value, \JSON_UNESCAPED_UNICODE | \JSON_PARTIAL_OUTPUT_ON_ERROR | \JSON_UNESCAPED_LINE_TERMINATORS | \JSON_UNESCAPED_SLASHES); -
klarna-payments-for-woocommerce/tags/4.13.4/dependencies/krokedil/wp-api/src/Request.php
r3150014 r3740915 9 9 10 10 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Logger; 11 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Masking; 13 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\FieldMasker; 11 14 /** 12 15 * Base request class for the package. … … 15 18 { 16 19 /** 20 * The reserved rule key that turns a rule into an allow list. 21 */ 22 const MASK_KEEP = FieldMasker::KEEP; 23 /** 17 24 * Config values. 18 25 * … … 57 64 public $method = 'GET'; 58 65 /** 66 * Fields to mask in the request args before logging. Keys map to a list of field names or 67 * to nested arrays, matched case insensitively wherever the name appears below that level. 68 * The reserved 'keep' key turns a rule into an allow list, masking every key it does not name. 69 * 70 * Example: 71 * array( 72 * 'headers' => array( 'Authorization' ), 73 * 'body' => array( 74 * 'billing_address' => array( 'email', 'phone' ), 75 * 'customer' => array( 'keep' => array( 'type' ) ), 76 * 'attachment' => 'mask', 77 * ), 78 * ) 79 * 80 * @var array 81 */ 82 protected $request_fields_to_mask = array('headers' => array('Authorization')); 83 /** 84 * Fields to mask in the response body before logging. Uses the same structure as the 85 * request fields, including the 'keep' allow list. 86 * 87 * Example: 88 * array( 89 * 'client_token', 90 * 'billing_address' => array( 'email', 'phone' ), 91 * ) 92 * 93 * @var array 94 */ 95 protected $response_fields_to_mask = array(); 96 /** 97 * Fields to mask in the request arguments before logging, same structure as above. 98 * 99 * @var array 100 */ 101 protected $argument_fields_to_mask = array('username', 'password'); 102 /** 59 103 * Constructor. 60 104 * 61 * @param array $config Configuration array. 62 */ 63 public function __construct($config = array(), $settings = array(), $arguments = array()) 105 * @param array $config Configuration array. 106 * @param array $settings Plugin settings. 107 * @param array $arguments Request arguments. 108 * @param array $masked_fields Optional extra fields to mask, with keys 'request', 'response' and/or 'arguments'. 109 * Merged into the configured fields, so an allow list can be passed here as well. 110 */ 111 public function __construct($config = array(), $settings = array(), $arguments = array(), $masked_fields = array()) 64 112 { 65 113 $this->config = wp_parse_args($config, $this->defaults); 66 114 $this->settings = $settings; 67 115 $this->arguments = $arguments; 116 if (!empty($masked_fields['request'])) { 117 $this->request_fields_to_mask = FieldMasker::merge_config($this->request_fields_to_mask, $masked_fields['request']); 118 } 119 if (!empty($masked_fields['response'])) { 120 $this->response_fields_to_mask = FieldMasker::merge_config($this->response_fields_to_mask, $masked_fields['response']); 121 } 122 if (!empty($masked_fields['arguments'])) { 123 $this->argument_fields_to_mask = FieldMasker::merge_config($this->argument_fields_to_mask, $masked_fields['arguments']); 124 } 68 125 } 69 126 /** … … 125 182 { 126 183 if (is_wp_error($response)) { 184 $this->log_response($response, $request_args, $request_url); 127 185 return $response; 128 186 } … … 131 189 $return = $this->get_error_message($response); 132 190 } else { 133 $return = \json_decode(wp_remote_retrieve_body($response), \true);191 $return = $this->get_response_body($response); 134 192 } 135 193 $this->log_response($response, $request_args, $request_url); … … 150 208 } 151 209 // Get the response body if its not a WP_Error. 152 $response_body = !is_wp_error($response) ? \json_decode(wp_remote_retrieve_body($response), \true) : array();210 $response_body = !is_wp_error($response) ? $this->get_response_body($response) : array(); 153 211 $code = wp_remote_retrieve_response_code($response); 154 // Parse the Request body into an array if its json format. 155 $request_body = $request_args['body'] ?? ''; 156 $decoded_body = \json_decode($request_body); 157 $request_args['body'] = $decoded_body ?? $request_args['body'] ?? null; 158 $request_args = $this->sanitize_request_args($request_args); 159 $arguments = $this->arguments; 160 if (isset($arguments['username'])) { 161 $arguments['username'] = '[REDACTED]'; 162 } 163 if (isset($arguments['password'])) { 164 $arguments['password'] = '[REDACTED]'; 212 // Set log level. Read from the body before it is masked. A body that failed to parse is a WP_Error. 213 $body_status = \is_array($response_body) ? $response_body['status'] ?? null : null; 214 if ($code < 200 || $code > 299 || is_wp_error($response_body)) { 215 $log_level = 'error'; 216 } elseif ('error' === $body_status) { 217 // Some APIs (e.g. Fraktjakt) return a successful HTTP status while reporting an 218 // application-level error in the response body. Treat those as errors too. 219 $log_level = 'error'; 220 } elseif ('warning' === $body_status) { 221 $log_level = 'warning'; 222 } else { 223 $log_level = 'info'; 224 } 225 $mask = Masking::is_enabled($this->config['slug']); 226 if ($mask) { 227 $response_body = $this->mask_response($response_body); 228 $arguments = $this->mask_arguments($this->arguments); 229 $request_url = $this->get_masked_request_url($request_url); 230 $request_args = $this->mask_request_args($request_args); 231 } else { 232 $arguments = $this->arguments; 233 if (isset($request_args['body']) && \is_string($request_args['body'])) { 234 $request_args['body'] = $this->decode_body($request_args['body']); 235 } 165 236 } 166 237 // Log the response. … … 172 243 'request_url' => $request_url, 173 244 'response' => array('body' => $response_body, 'code' => $code), 245 'log_level' => $log_level, 174 246 'timestamp' => \date('Y-m-d H:i:s'), 175 247 // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display. 176 'stack' => Logger::get_stack($this->config['extended_debugging'] ),248 'stack' => Logger::get_stack($this->config['extended_debugging'], $mask), 177 249 'plugin_version' => $this->config['plugin_version'], 178 250 )); 179 251 } 180 252 /** 253 * Returns the body of the response based on the content type. 254 * 255 * @param array|\WP_Error $response The response from wp_remote_get() or wp_remote_post(). 256 * @return array|object|string|\WP_Error Parsed response body or WP_Error if an error occurs. 257 */ 258 public function get_response_body($response) 259 { 260 if (is_wp_error($response)) { 261 return $response; 262 } 263 $content_type = wp_remote_retrieve_header($response, 'content-type'); 264 $content_type = \is_array($content_type) ? \implode(', ', $content_type) : (string) $content_type; 265 $body = wp_remote_retrieve_body($response); 266 if (\strpos($content_type, 'application/json') !== \false) { 267 $parsed_body = \json_decode($body, \true); 268 if (\json_last_error() === \JSON_ERROR_NONE) { 269 return $parsed_body; 270 } 271 return new \WP_Error('json_decode_error', 'Failed to decode JSON'); 272 } elseif (\strpos($content_type, 'text/html') !== \false) { 273 return $body; 274 } elseif (\strpos($content_type, 'application/xml') !== \false || \strpos($content_type, 'text/xml') !== \false) { 275 // Collect parse errors rather than emitting a PHP warning per malformed node. 276 $use_errors = \libxml_use_internal_errors(\true); 277 $parsed_body = \simplexml_load_string($body); 278 \libxml_clear_errors(); 279 \libxml_use_internal_errors($use_errors); 280 if (\false !== $parsed_body) { 281 return \json_decode(wp_json_encode($parsed_body), \true); 282 } 283 return new \WP_Error('xml_parse_error', 'Failed to parse XML'); 284 } else { 285 // No or an unknown content type. Decode json anyway, so a body the API did not label 286 // is still returned as an array and the response masking rules can reach into it. 287 $parsed_body = \json_decode($body, \true); 288 return \is_array($parsed_body) ? $parsed_body : $body; 289 } 290 } 291 /** 292 * Mask sensitive fields in the request args before logging. Reading the configuration 293 * happens inside the guard, so a broken config costs the section and not the request. 294 * 295 * @param array $request_args The request args. 296 * @return array|string The masked request args, or the failure marker. 297 */ 298 protected function mask_request_args($request_args) 299 { 300 try { 301 // Decode the body that was really sent, so the rules can reach into it and the log 302 // holds a readable structure rather than an escaped string. A body no rule can reach 303 // into is masked whole by a configured body rule, see FieldMasker. 304 if (isset($request_args['body']) && \is_string($request_args['body'])) { 305 $request_args['body'] = $this->decode_body($request_args['body']); 306 } 307 $fields = $this->request_fields_to_mask; 308 return empty($fields) ? $request_args : $this->sanitize_field($request_args, $fields); 309 } catch (\Throwable $e) { 310 return KeyMasker::FAILED; 311 } 312 } 313 /** 314 * Decode a json or form encoded body string into an array, or return it as it was. 315 * 316 * @param string $body The body string. 317 * @return array|string 318 */ 319 private function decode_body($body) 320 { 321 $decoded = \json_decode($body, \true); 322 if (\is_array($decoded)) { 323 return $decoded; 324 } 325 // What http_build_query() produces: key=value pairs joined by '&', nothing else. 326 if (\preg_match('/^[^=&\\s]+=[^&\\s]*(?:&[^=&\\s]+=[^&\\s]*)*$/', $body)) { 327 \parse_str($body, $decoded); 328 return $decoded; 329 } 330 return $body; 331 } 332 /** 333 * Mask sensitive fields in the response body before logging. 334 * 335 * @param array|\WP_Error $response The decoded response body. 336 * @return array|string|\WP_Error The masked response body, or the failure marker. 337 */ 338 protected function mask_response($response) 339 { 340 if (is_wp_error($response) || empty($response)) { 341 return $response; 342 } 343 try { 344 $fields = $this->response_fields_to_mask; 345 return empty($fields) ? $response : $this->sanitize_field($response, $fields); 346 } catch (\Throwable $e) { 347 return KeyMasker::FAILED; 348 } 349 } 350 /** 351 * Mask sensitive fields in the request arguments before logging. 352 * 353 * @param array $arguments The request arguments. 354 * @return array|string The masked arguments, or the failure marker. 355 */ 356 protected function mask_arguments($arguments) 357 { 358 try { 359 $fields = $this->argument_fields_to_mask; 360 return empty($fields) ? $arguments : $this->sanitize_field($arguments, $fields); 361 } catch (\Throwable $e) { 362 return KeyMasker::FAILED; 363 } 364 } 365 /** 366 * Mask the request URL before logging, unchanged by default. Override it when the API 367 * addresses a resource by a token in the path, such as /authorizations/{token}/order. 368 * 369 * @param string $request_url The request URL. 370 * @return string 371 */ 372 protected function mask_request_url($request_url) 373 { 374 return $request_url; 375 } 376 /** 377 * Call mask_request_url() without letting an override that throws kill the API call. 378 * The fallback is a redaction, never the raw URL. 379 * 380 * @param string $request_url The request URL. 381 * @return string 382 */ 383 private function get_masked_request_url($request_url) 384 { 385 try { 386 $masked = $this->mask_request_url($request_url); 387 return \is_string($masked) ? $masked : KeyMasker::FAILED; 388 } catch (\Throwable $e) { 389 return KeyMasker::FAILED; 390 } 391 } 392 /** 393 * Recursively mask a set of fields within a data array. A rule keeps matching below the 394 * level it was declared on, so a container nested in a list is treated like one at the root. 395 * 396 * @param array $data The data array to sanitize. 397 * @param array $fields_to_sanitize The fields to sanitize. 398 * @return array 399 */ 400 protected function sanitize_field($data, $fields_to_sanitize) 401 { 402 return FieldMasker::mask($data, $fields_to_sanitize); 403 } 404 /** 181 405 * Remove sensitive data from the log. 182 406 * 407 * @deprecated Use mask_request_args() instead. Kept for backward compatibility. 183 408 * @param array $request_args The request data to sanitize. 184 * @return array The request data sanitized.409 * @return array|string The request data sanitized, or the failure marker. 185 410 */ 186 411 protected function sanitize_request_args($request_args) 187 412 { 188 // Do not log the authorization token. 189 foreach ($request_args['headers'] as $header => $value) { 190 if ('authorization' === \strtolower($header)) { 191 // If it is longer than 15 char., it most likely has a token. This is an assumption that is safe even if it is wrong. 192 $request_args['headers'][$header] = \strlen($value) > 15 ? '[REDACTED]' : '[MISSING]'; 193 break; 194 } 195 } 196 return $request_args; 413 return $this->mask_request_args($request_args); 197 414 } 198 415 /** -
klarna-payments-for-woocommerce/tags/4.13.4/klarna-payments-for-woocommerce.php
r3728366 r3740915 6 6 * Author: klarna 7 7 * Author URI: https://www.klarna.com/ 8 * Version: 4.13. 38 * Version: 4.13.4 9 9 * Text Domain: klarna-payments-for-woocommerce 10 10 * Domain Path: /languages 11 11 * 12 12 * WC requires at least: 5.6.0 13 * WC tested up to: 11. 1.213 * WC tested up to: 11.2.1 14 14 * Requires Plugins: woocommerce 15 15 * … … 38 38 use Krokedil\Klarna\ExpressCheckout; 39 39 use Krokedil\Klarna\Utilities\ApiCredentialsUtility; 40 use Krokedil\Klarna\Logging\LogMasking; 41 use Krokedil\Klarna\Logging\LogWriter; 40 42 41 43 if ( ! defined( 'ABSPATH' ) ) { … … 53 55 * Required minimums and constants 54 56 */ 55 define( 'WC_KLARNA_PAYMENTS_VERSION', '4.13. 3' );57 define( 'WC_KLARNA_PAYMENTS_VERSION', '4.13.4' ); 56 58 define( 'WC_KLARNA_PAYMENTS_MIN_PHP_VER', '7.4.0' ); 57 59 define( 'WC_KLARNA_PAYMENTS_MIN_WC_VER', '5.6.0' ); … … 331 333 $this->order_management = new OrderManagement(); 332 334 $this->logger = new Logger( 'klarna_payments', 'no' !== ( $settings['logging'] ?? 'no' ) ); 335 336 // Masking has to be in place before anything can log. 337 LogMasking::register(); 338 LogWriter::register(); 333 339 Compatibility::register(); 334 340 -
klarna-payments-for-woocommerce/tags/4.13.4/languages/klarna-payments-for-woocommerce.pot
r3728366 r3740915 6 6 "Report-Msgid-Bugs-To: " 7 7 "https://wordpress.org/support/plugin/klarna-payments-for-woocommerce\n" 8 "POT-Creation-Date: 2026-10- 05 08:10:20+00:00\n"8 "POT-Creation-Date: 2026-10-12 09:04:01+00:00\n" 9 9 "MIME-Version: 1.0\n" 10 10 "Content-Type: text/plain; charset=utf-8\n" … … 513 513 msgstr "" 514 514 515 #: classes/class-kp-assets.php:2 50515 #: classes/class-kp-assets.php:283 516 516 #. translators: [customer-facing]. 517 517 msgid "Please read and accept the terms and conditions to proceed with your order." 518 518 msgstr "" 519 519 520 #: classes/class-kp-assets.php:3 01520 #: classes/class-kp-assets.php:334 521 521 #. translators: [merchant-facing]. 522 522 msgid "Select all" … … 728 728 #: dependencies/krokedil/settings-page/src/Navigation.php:54 729 729 #: dependencies/krokedil/settings-page/src/Support.php:36 730 #: klarna-payments-for-woocommerce.php:4 49730 #: klarna-payments-for-woocommerce.php:455 731 731 #. translators: [merchant-facing]. 732 732 msgid "Support" … … 879 879 msgstr "" 880 880 881 #: includes/kp-functions.php:738 klarna-payments-for-woocommerce.php:6 65881 #: includes/kp-functions.php:738 klarna-payments-for-woocommerce.php:671 882 882 #. translators: [customer-facing]. 883 883 msgid "Pay with Klarna" 884 884 msgstr "" 885 885 886 #: klarna-payments-for-woocommerce.php:4 45886 #: klarna-payments-for-woocommerce.php:451 887 887 #. translators: [merchant-facing]. 888 888 msgid "Settings" 889 889 msgstr "" 890 890 891 #: klarna-payments-for-woocommerce.php:4 47891 #: klarna-payments-for-woocommerce.php:453 892 892 #. translators: [merchant-facing]. 893 893 msgid "Docs" 894 894 msgstr "" 895 895 896 #: klarna-payments-for-woocommerce.php:49 1896 #: klarna-payments-for-woocommerce.php:497 897 897 #. translators: [merchant-facing]. 898 898 msgid "The WooCommerce plugin must be active for Klarna Payments to work." 899 899 msgstr "" 900 900 901 #: klarna-payments-for-woocommerce.php:51 0901 #: klarna-payments-for-woocommerce.php:516 902 902 #. translators: [merchant-facing]. 903 903 msgid "Dismiss" 904 904 msgstr "" 905 905 906 #: klarna-payments-for-woocommerce.php:5 17906 #: klarna-payments-for-woocommerce.php:523 907 907 #. translators: [merchant-facing]. URL to docs. 908 908 msgid "" … … 912 912 msgstr "" 913 913 914 #: klarna-payments-for-woocommerce.php:61 1915 #: klarna-payments-for-woocommerce.php:62 0914 #: klarna-payments-for-woocommerce.php:617 915 #: klarna-payments-for-woocommerce.php:626 916 916 msgid "" 917 917 "Your installation of Klarna Payments is not complete. If you installed this " … … 920 920 msgstr "" 921 921 922 #: klarna-payments-for-woocommerce.php:6 55922 #: klarna-payments-for-woocommerce.php:661 923 923 #. translators: [customer-facing]. 924 924 msgid "Pay with Klarna (free)" … … 942 942 943 943 #: src/ExpressCheckout/Api/Notifications/PaymentStateExpired.php:43 944 #: src/ExpressCheckout/KECOneStepIntegration.php:10 1944 #: src/ExpressCheckout/KECOneStepIntegration.php:102 945 945 #. translators: [merchant-facing]. 946 946 msgid "Order cancelled due to expired payment request." … … 1478 1478 msgstr "" 1479 1479 1480 #: src/OrderManagement/Request/Request.php:16 41480 #: src/OrderManagement/Request/Request.php:165 1481 1481 #. translators: [merchant-facing]. 1482 1482 msgid "Invalid JSON response from the server." -
klarna-payments-for-woocommerce/tags/4.13.4/readme.txt
r3728366 r3740915 7 7 Requires PHP: 8.0 8 8 WC requires at least: 5.6.0 9 WC tested up to: 11. 1.210 Stable tag: 4.13. 39 WC tested up to: 11.2.1 10 Stable tag: 4.13.4 11 11 License: GPLv3 or later 12 12 License URI: http://www.gnu.org/licenses/gpl-3.0.html … … 70 70 71 71 == Changelog == 72 = 2026-10-12 - version 4.13.4 = 73 * Enhancement - Masked customer personal, and other sensitive data and credentials in the plugin log. 74 * Fix - Order details are now only added to the pay for order page when WooCommerce would show the payment form: the request carries the order's key, the customer may pay for the order, the order still needs payment, and a guest has confirmed the billing email. 75 72 76 = 2026-10-05 - version 4.13.3 = 73 77 * Enhancement - Improved handling of Klarna authorization callbacks and background processing. -
klarna-payments-for-woocommerce/tags/4.13.4/src/ExpressCheckout/KECOneStepIntegration.php
r3716655 r3740915 94 94 95 95 if ( ! $payment_request_id || ! $interoperability_token ) { 96 KP_WC()->logger()->error( '[KEC One Step] Missing required fields in payload for expired payment: ' . wp_json_encode( $payload ) ); 96 $missing = $payment_request_id ? 'interoperability_token' : 'payment_request_id'; 97 KP_WC()->logger()->error( "[KEC One Step] Missing {$missing} in the payload for expired payment on order {$order->get_id()}." ); 97 98 return; 98 99 } -
klarna-payments-for-woocommerce/tags/4.13.4/src/OrderManagement/Request/Request.php
r3664907 r3740915 3 3 4 4 use Krokedil\Klarna\OrderManagement; 5 use Krokedil\Klarna\Logging\LogMasking; 5 6 use Krokedil\Klarna\Utilities\ApiCredentialsUtility; 6 7 … … 355 356 */ 356 357 protected function log_response( $response, $request_args, $request_url, $code ) { 357 foreach ( $request_args['headers'] as $header => $value ) {358 if ( 'authorization' === strtolower( $header ) ) {359 // If it is longer than 15 char., it most likely has a token. This is an assumption that is safe even if it is wrong.360 $request_args['headers'][ $header ] = strlen( $value ) > 15 ? '[REDACTED]' : '[MISSING]'; 361 break;362 }363 } 364 $log = \KP_Logger::format_log( $this->klarna_order_id, $this->method, $this->log_title, $request_args, $ response, $code, $request_url );358 // Decode the body so the masking can reach into it, and drop the rest of the HTTP 359 // response, which holds nothing a log needs and hides the payload inside a string. 360 $body = is_wp_error( $response ) ? array() : json_decode( wp_remote_retrieve_body( $response ), true ); 361 362 $request_args = LogMasking::mask_request( $request_args ); 363 $body = LogMasking::mask_response( $body ); 364 365 $log = \KP_Logger::format_log( $this->klarna_order_id, $this->method, $this->log_title, $request_args, $body, $code, $request_url ); 365 366 \KP_Logger::log( $log ); 366 367 } -
klarna-payments-for-woocommerce/tags/4.13.4/vendor/composer/autoload_classmap.php
r3664907 r3740915 44 44 'Krokedil\\Klarna\\ExpressCheckout\\WebhookSetup' => $baseDir . '/src/ExpressCheckout/WebhookSetup.php', 45 45 'Krokedil\\Klarna\\Features' => $baseDir . '/src/Features.php', 46 'Krokedil\\Klarna\\Logging\\LogMasking' => $baseDir . '/src/Logging/LogMasking.php', 47 'Krokedil\\Klarna\\Logging\\LogWriter' => $baseDir . '/src/Logging/LogWriter.php', 46 48 'Krokedil\\Klarna\\OnsiteMessaging\\Blocks\\CartBlockIntegration' => $baseDir . '/src/OnsiteMessaging/Blocks/CartBlockIntegration.php', 47 49 'Krokedil\\Klarna\\OnsiteMessaging\\KlarnaOnsiteMessaging' => $baseDir . '/src/OnsiteMessaging/KlarnaOnsiteMessaging.php', -
klarna-payments-for-woocommerce/tags/4.13.4/vendor/composer/autoload_static.php
r3696456 r3740915 67 67 'Krokedil\\Klarna\\ExpressCheckout\\WebhookSetup' => __DIR__ . '/../..' . '/src/ExpressCheckout/WebhookSetup.php', 68 68 'Krokedil\\Klarna\\Features' => __DIR__ . '/../..' . '/src/Features.php', 69 'Krokedil\\Klarna\\Logging\\LogMasking' => __DIR__ . '/../..' . '/src/Logging/LogMasking.php', 70 'Krokedil\\Klarna\\Logging\\LogWriter' => __DIR__ . '/../..' . '/src/Logging/LogWriter.php', 69 71 'Krokedil\\Klarna\\OnsiteMessaging\\Blocks\\CartBlockIntegration' => __DIR__ . '/../..' . '/src/OnsiteMessaging/Blocks/CartBlockIntegration.php', 70 72 'Krokedil\\Klarna\\OnsiteMessaging\\KlarnaOnsiteMessaging' => __DIR__ . '/../..' . '/src/OnsiteMessaging/KlarnaOnsiteMessaging.php', -
klarna-payments-for-woocommerce/tags/4.13.4/vendor/composer/installed.php
r3728366 r3740915 2 2 'root' => array( 3 3 'name' => '__root__', 4 'pretty_version' => '4.13. 3',5 'version' => '4.13. 3.0',6 'reference' => ' 0d6c8537e224d077da2c9b85736448423cc02baf',4 'pretty_version' => '4.13.4', 5 'version' => '4.13.4.0', 6 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 7 7 'type' => 'library', 8 8 'install_path' => __DIR__ . '/../../', … … 12 12 'versions' => array( 13 13 '__root__' => array( 14 'pretty_version' => '4.13. 3',15 'version' => '4.13. 3.0',16 'reference' => ' 0d6c8537e224d077da2c9b85736448423cc02baf',14 'pretty_version' => '4.13.4', 15 'version' => '4.13.4.0', 16 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 17 17 'type' => 'library', 18 18 'install_path' => __DIR__ . '/../../', -
klarna-payments-for-woocommerce/trunk/changelog.txt
r3728366 r3740915 1 1 *** Klarna for WooCommerce Changelog *** 2 2026-10-12 - version 4.13.4 3 * Enhancement - Masked customer personal, and other sensitive data and credentials in the plugin log. 4 * Fix - Order details are now only added to the pay for order page when WooCommerce would show the payment form: the request carries the order's key, the customer may pay for the order, the order still needs payment, and a guest has confirmed the billing email. 5 2 6 2026-10-05 - version 4.13.3 3 7 * Enhancement - Improved handling of Klarna authorization callbacks and background processing. -
klarna-payments-for-woocommerce/trunk/classes/class-kp-ajax.php
r3728366 r3740915 100 100 wp_send_json_success( $order->get_checkout_order_received_url() ); 101 101 } else { 102 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Auth token: %s. %s', $order->get_id(), $auth_token, $recurring_token->get_error_message() ) );102 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Failed to create a recurring token: %s', $order->get_id(), $recurring_token->get_error_message() ) ); 103 103 wp_send_json_error( 'customer_token_failed' ); 104 104 } -
klarna-payments-for-woocommerce/trunk/classes/class-kp-assets.php
r3716655 r3740915 6 6 */ 7 7 8 use Automattic\WooCommerce\Internal\Utilities\Users; 8 9 use Krokedil\Klarna\Features; 9 10 use Krokedil\Klarna\PluginFeatures; … … 188 189 189 190 /** 191 * Whether WooCommerce asks the guest to confirm the billing email before showing the order-pay form. 192 * 193 * @param WC_Order $order The order being paid for. 194 * @return bool 195 */ 196 private function guest_should_verify_email( $order ) { 197 // WooCommerce versions without the check never ask for verification. 198 if ( ! is_callable( array( Users::class, 'should_user_verify_order_email' ) ) ) { 199 return false; 200 } 201 202 // Mirrors WC_Shortcode_Checkout::guest_should_verify_email(), which accepts the email posted by its verify form. 203 $supplied_email = null; 204 if ( wp_verify_nonce( (string) filter_input( INPUT_POST, 'check_submission' ), 'wc_verify_email' ) ) { 205 $supplied_email = sanitize_email( wp_unslash( (string) filter_input( INPUT_POST, 'email' ) ) ); 206 } 207 208 return Users::should_user_verify_order_email( $order->get_id(), $supplied_email, 'order-pay' ); 209 } 210 211 /** 190 212 * Get the params for the Klarna Payments checkout script. 191 213 * … … 200 222 201 223 if ( ! empty( $order_id ) ) { 202 $order = wc_get_order( $order_id ); 203 if ( ! empty( $order ) ) { 224 $order = wc_get_order( $order_id ); 225 $request_key = (string) filter_input( INPUT_GET, 'key', FILTER_SANITIZE_SPECIAL_CHARS ); 226 227 // Only expose the order when WooCommerce would render the pay form, so the same checks in the same order. 228 if ( 229 ! empty( $order ) 230 && hash_equals( $order->get_order_key(), $request_key ) 231 && current_user_can( 'pay_for_order', $order_id ) 232 && $order->needs_payment() 233 && ! $this->guest_should_verify_email( $order ) 234 ) { 204 235 $order_key = $order->get_order_key(); 236 } else { 237 $order_id = null; 205 238 } 206 239 } … … 240 273 'order_key' => $order_key, 241 274 'submit_button_selectors' => array( 'input#place_order', 'button#place_order' ), 242 'addresses' => $pay_for_order? array(275 'addresses' => null !== $order_key ? array( 243 276 'billing' => $customer['billing'], 244 277 'shipping' => $customer['shipping'], -
klarna-payments-for-woocommerce/trunk/classes/class-kp-callbacks.php
r3728366 r3740915 241 241 $order->add_order_note( __( 'Failed to create a recurring token when returning from the hosted payment page.', 'klarna-payments-for-woocommerce' ) . $recurring_token->get_error_message() ); 242 242 243 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Auth token: %s. %s', $order->get_id(), $auth_token, $recurring_token->get_error_message() ) );243 KP_Logger::log( sprintf( '[AJAX]: Order ID: %s. Failed to create a recurring token: %s', $order->get_id(), $recurring_token->get_error_message() ) ); 244 244 } 245 245 -
klarna-payments-for-woocommerce/trunk/classes/class-kp-logger.php
r3716655 r3740915 9 9 exit; // Exit if accessed directly. 10 10 } 11 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 11 13 12 14 /** … … 28 30 public static function log( $data ) { 29 31 $kp_settings = get_option( 'woocommerce_klarna_payments_settings', array() ); 30 if ( 'no' !== ( $kp_settings['logging'] ?? 'no' ) ) { 31 $message = self::format_data( $data ); 32 KP_WC()->logger()->info( wp_json_encode( $message ) ); 32 if ( 'no' === ( $kp_settings['logging'] ?? 'no' ) ) { 33 return; 33 34 } 35 36 $message = self::format_data( $data ); 37 38 // A failure here costs the entry, it never lets an unmasked one through. 39 try { 40 $message = KeyMasker::mask( self::format_data( $data ) ); 41 } catch ( \Throwable $e ) { 42 $message = array( 'error' => KeyMasker::FAILED ); 43 } 44 45 KP_WC()->logger()->info( wp_json_encode( $message ) ); 34 46 } 35 47 … … 41 53 */ 42 54 public static function format_data( $data ) { 43 if ( isset( $data['request']['body'] ) ) {55 if ( isset( $data['request']['body'] ) && is_string( $data['request']['body'] ) ) { 44 56 $request_body = json_decode( $data['request']['body'], true ); 45 57 $data['request']['body'] = $request_body; -
klarna-payments-for-woocommerce/trunk/classes/requests/class-kp-requests.php
r3664907 r3740915 9 9 10 10 use Krokedil\Klarna\Utilities\ApiCredentialsUtility; 11 use Krokedil\Klarna\Logging\LogMasking; 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 11 13 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Request; 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Logger;13 14 14 15 /** … … 87 88 ); 88 89 89 parent::__construct( $config, $settings, $arguments );90 parent::__construct( $config, $settings, $arguments, self::get_masked_fields() ); 90 91 $this->set_credentials(); 91 92 $this->iframe_options = new KP_IFrame( $settings ); … … 285 286 286 287 /** 287 * Logs the response from the request. 288 * 289 * @param array|\WP_Error $response The response from the request. 290 * @param array $request_args The request args. 291 * @param string $request_url The request URL. 292 * 293 * @return void 294 */ 295 protected function log_response( $response, $request_args, $request_url ) { 296 $code = wp_remote_retrieve_response_code( $response ); 297 298 // Get the response body if its not a WP_Error. 299 $response_body = is_wp_error( $response ) ? array() : json_decode( wp_remote_retrieve_body( $response ), true ); 300 301 // Parse the Request body into an array if its json format. 302 $request_body = $request_args['body'] ?? ''; 303 $decoded_body = json_decode( $request_body ); 304 $request_args['body'] = $decoded_body ?? $request_args['body'] ?? null; 305 $request_args = $this->sanitize_request_args( $request_args ); 306 307 $arguments = $this->arguments; 308 if ( isset( $arguments['username'] ) ) { 309 $arguments['username'] = '[REDACTED]'; 310 } 311 if ( isset( $arguments['password'] ) ) { 312 $arguments['password'] = '[REDACTED]'; 313 } 314 315 KP_WC()->logger()->info( 316 wp_json_encode( 317 array( 318 'type' => $this->method, 319 'title' => $this->log_title, 320 'arguments' => $arguments, 321 'request' => $request_args, 322 'request_url' => $request_url, 323 'response' => array( 324 'body' => $response_body, 325 'code' => $code, 326 ), 327 'timestamp' => date( 'Y-m-d H:i:s' ), // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display. 328 'stack' => Logger::get_stack( $this->config['extended_debugging'] ), 329 'plugin_version' => $this->config['plugin_version'], 330 ) 331 ) 288 * The fields masked out of the log, merged into what the package already masks. 289 * 290 * @return array 291 */ 292 protected static function get_masked_fields() { 293 return array( 294 'request' => LogMasking::request_fields(), 295 'response' => LogMasking::response_fields(), 296 'arguments' => array( 'api_password', 'auth_token', 'recurring_token' ), 297 ); 298 } 299 300 /** 301 * Mask the tokens Klarna addresses a resource by in the path. A session or order id is 302 * left readable, since neither authorises anything and both are what a log is searched by. 303 * 304 * @param string $request_url The request URL. 305 * @return string 306 */ 307 protected function mask_request_url( $request_url ) { 308 return preg_replace( 309 array( '#/authorizations/[^/?]+#', '#/tokens/[^/?]+#' ), 310 array( '/authorizations/' . KeyMasker::REDACTED, '/tokens/' . KeyMasker::REDACTED ), 311 $request_url 332 312 ); 333 313 } -
klarna-payments-for-woocommerce/trunk/classes/requests/post/class-kp-unavailable-features.php
r3664907 r3740915 127 127 return new WP_Error( $code, $error_message ); 128 128 } 129 130 /**131 * Logs the response from the request.132 *133 * @param array|\WP_Error $response The response from the request.134 * @param array $request_args The request args.135 * @param string $request_url The request URL.136 * @return void137 */138 protected function log_response( $response, $request_args, $request_url ) {139 $this->arguments['api_password'] = '[REDACTED]';140 parent::log_response( $response, $request_args, $request_url );141 }142 129 } -
klarna-payments-for-woocommerce/trunk/dependencies/autoload.php
r3728366 r3740915 20 20 require_once __DIR__ . '/composer/autoload_real.php'; 21 21 22 return ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e3::getLoader();22 return ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386::getLoader(); -
klarna-payments-for-woocommerce/trunk/dependencies/composer/autoload_classmap.php
r3728366 r3740915 58 58 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\StoreApi\\StoreApiLineShipping' => $vendorDir . '/krokedil/woocommerce/src/StoreApi/StoreApiLineShipping.php', 59 59 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\Subscription' => $vendorDir . '/krokedil/woocommerce/src/Subscription.php', 60 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\FieldMasker' => $vendorDir . '/krokedil/wp-api/src/FieldMasker.php', 61 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\KeyMasker' => $vendorDir . '/krokedil/wp-api/src/KeyMasker.php', 60 62 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Logger' => $vendorDir . '/krokedil/wp-api/src/Logger.php', 63 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Masking' => $vendorDir . '/krokedil/wp-api/src/Masking.php', 61 64 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Request' => $vendorDir . '/krokedil/wp-api/src/Request.php', 62 65 ); -
klarna-payments-for-woocommerce/trunk/dependencies/composer/autoload_real.php
r3728366 r3740915 3 3 // autoload_real.php @generated by Composer 4 4 5 class ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e35 class ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386 6 6 { 7 7 private static $loader; … … 23 23 } 24 24 25 spl_autoload_register(array('ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e3', 'loadClassLoader'), true, true);25 spl_autoload_register(array('ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386', 'loadClassLoader'), true, true); 26 26 self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(__DIR__)); 27 spl_autoload_unregister(array('ComposerAutoloaderInit c3911f91ac7a4070427b96bb312df1e3', 'loadClassLoader'));27 spl_autoload_unregister(array('ComposerAutoloaderInit82df93fcb7d0af6c2c95e9a438100386', 'loadClassLoader')); 28 28 29 29 require __DIR__ . '/autoload_static.php'; 30 call_user_func(\Composer\Autoload\ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::getInitializer($loader));30 call_user_func(\Composer\Autoload\ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::getInitializer($loader)); 31 31 32 32 $loader->register(true); -
klarna-payments-for-woocommerce/trunk/dependencies/composer/autoload_static.php
r3728366 r3740915 5 5 namespace Composer\Autoload; 6 6 7 class ComposerStaticInit c3911f91ac7a4070427b96bb312df1e37 class ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386 8 8 { 9 9 public static $prefixLengthsPsr4 = array ( … … 88 88 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\StoreApi\\StoreApiLineShipping' => __DIR__ . '/..' . '/krokedil/woocommerce/src/StoreApi/StoreApiLineShipping.php', 89 89 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WooCommerce\\Subscription' => __DIR__ . '/..' . '/krokedil/woocommerce/src/Subscription.php', 90 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\FieldMasker' => __DIR__ . '/..' . '/krokedil/wp-api/src/FieldMasker.php', 91 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\KeyMasker' => __DIR__ . '/..' . '/krokedil/wp-api/src/KeyMasker.php', 90 92 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Logger' => __DIR__ . '/..' . '/krokedil/wp-api/src/Logger.php', 93 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Masking' => __DIR__ . '/..' . '/krokedil/wp-api/src/Masking.php', 91 94 'KrokedilKlarnaPaymentsDeps\\Krokedil\\WpApi\\Request' => __DIR__ . '/..' . '/krokedil/wp-api/src/Request.php', 92 95 ); … … 95 98 { 96 99 return \Closure::bind(function () use ($loader) { 97 $loader->prefixLengthsPsr4 = ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::$prefixLengthsPsr4;98 $loader->prefixDirsPsr4 = ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::$prefixDirsPsr4;99 $loader->classMap = ComposerStaticInit c3911f91ac7a4070427b96bb312df1e3::$classMap;100 $loader->prefixLengthsPsr4 = ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::$prefixLengthsPsr4; 101 $loader->prefixDirsPsr4 = ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::$prefixDirsPsr4; 102 $loader->classMap = ComposerStaticInit82df93fcb7d0af6c2c95e9a438100386::$classMap; 100 103 101 104 }, null, ClassLoader::class); -
klarna-payments-for-woocommerce/trunk/dependencies/composer/installed.json
r3728366 r3740915 199 199 { 200 200 "name": "krokedil\/wp-api", 201 "version": "1. 1.1",202 "version_normalized": "1. 1.1.0",201 "version": "1.2.0", 202 "version_normalized": "1.2.0.0", 203 203 "source": { 204 204 "type": "git", 205 205 "url": "https:\/\/github.com\/krokedil\/wp-api.git", 206 "reference": "a3ba33f7ac640456dad36a33d711d20091c71aea" 207 }, 208 "dist": { 209 "type": "zip", 210 "url": "https:\/\/api.github.com\/repos\/krokedil\/wp-api\/zipball\/a3ba33f7ac640456dad36a33d711d20091c71aea", 211 "reference": "a3ba33f7ac640456dad36a33d711d20091c71aea", 212 "shasum": "" 213 }, 214 "require-dev": { 206 "reference": "3473dd2fb6b31ed5060ecd69877c98b5287ebe48" 207 }, 208 "dist": { 209 "type": "zip", 210 "url": "https:\/\/api.github.com\/repos\/krokedil\/wp-api\/zipball\/3473dd2fb6b31ed5060ecd69877c98b5287ebe48", 211 "reference": "3473dd2fb6b31ed5060ecd69877c98b5287ebe48", 212 "shasum": "" 213 }, 214 "require-dev": { 215 "dealerdirect\/phpcodesniffer-composer-installer": "^1.0", 215 216 "php": "~7.1 || ~8.0", 216 217 "php-stubs\/woocommerce-stubs": "^7.2", 217 "php-stubs\/wordpress-stubs": "^6.1" 218 }, 219 "time": "2024-04-22T09:03:39+00:00", 218 "php-stubs\/wordpress-stubs": "^6.1", 219 "phpstan\/phpstan": "^2.0", 220 "phpunit\/phpunit": "^9.6", 221 "squizlabs\/php_codesniffer": "^3.13", 222 "szepeviktor\/phpstan-wordpress": "^2.0", 223 "wp-coding-standards\/wpcs": "^3.1" 224 }, 225 "time": "2026-10-07T08:46:15+00:00", 220 226 "type": "library", 221 227 "installation-source": "dist", … … 225 231 } 226 232 }, 233 "scripts": { 234 "test": [ 235 "phpunit" 236 ], 237 "phpcs": [ 238 "phpcs" 239 ], 240 "phpcbf": [ 241 "phpcbf" 242 ], 243 "phpstan": [ 244 "phpstan analyse --memory-limit=1G" 245 ] 246 }, 227 247 "license": [ 228 248 "GPL-V2" … … 236 256 "description": "A composer library for Krokedil WordPress plugins that contains classes for working with wp_remote_request to setup request classes. Also uses the WooCommerce logger to log request reponses.", 237 257 "support": { 238 "source": "https:\/\/github.com\/krokedil\/wp-api\/tree\/1. 1.1",258 "source": "https:\/\/github.com\/krokedil\/wp-api\/tree\/1.2.0", 239 259 "issues": "https:\/\/github.com\/krokedil\/wp-api\/issues" 240 260 }, -
klarna-payments-for-woocommerce/trunk/dependencies/composer/installed.php
r3728366 r3740915 3 3 namespace KrokedilKlarnaPaymentsDeps; 4 4 5 return array('root' => array('name' => '__root__', 'pretty_version' => '4.13. 3', 'version' => '4.13.3.0', 'reference' => '0d6c8537e224d077da2c9b85736448423cc02baf', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \true), 'versions' => array('__root__' => array('pretty_version' => '4.13.3', 'version' => '4.13.3.0', 'reference' => '0d6c8537e224d077da2c9b85736448423cc02baf', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/settings-page' => array('pretty_version' => '1.3.7', 'version' => '1.3.7.0', 'reference' => 'ef40906dd574b8c61d5831d247661a909c0463e6', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/settings-page', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/support' => array('pretty_version' => '1.0.3', 'version' => '1.0.3.0', 'reference' => 'e4ee49d1c862e45fb284247c84d7c1f5583cb6ba', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/support', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/woocommerce' => array('pretty_version' => '1.9.2', 'version' => '1.9.2.0', 'reference' => '6c38cf569df5eeb3598f206d449eca86ae9ac402', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/woocommerce', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/wp-api' => array('pretty_version' => '1.1.1', 'version' => '1.1.1.0', 'reference' => 'a3ba33f7ac640456dad36a33d711d20091c71aea', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/wp-api', 'aliases' => array(), 'dev_requirement' => \false)));5 return array('root' => array('name' => '__root__', 'pretty_version' => '4.13.4', 'version' => '4.13.4.0', 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev' => \true), 'versions' => array('__root__' => array('pretty_version' => '4.13.4', 'version' => '4.13.4.0', 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/settings-page' => array('pretty_version' => '1.3.7', 'version' => '1.3.7.0', 'reference' => 'ef40906dd574b8c61d5831d247661a909c0463e6', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/settings-page', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/support' => array('pretty_version' => '1.0.3', 'version' => '1.0.3.0', 'reference' => 'e4ee49d1c862e45fb284247c84d7c1f5583cb6ba', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/support', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/woocommerce' => array('pretty_version' => '1.9.2', 'version' => '1.9.2.0', 'reference' => '6c38cf569df5eeb3598f206d449eca86ae9ac402', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/woocommerce', 'aliases' => array(), 'dev_requirement' => \false), 'krokedil/wp-api' => array('pretty_version' => '1.2.0', 'version' => '1.2.0.0', 'reference' => '3473dd2fb6b31ed5060ecd69877c98b5287ebe48', 'type' => 'library', 'install_path' => __DIR__ . '/../krokedil/wp-api', 'aliases' => array(), 'dev_requirement' => \false))); -
klarna-payments-for-woocommerce/trunk/dependencies/krokedil/wp-api/CHANGELOG.md
r3150014 r3740915 7 7 8 8 ## [Unreleased] 9 10 ------------------ 11 ## [1.2.0] - 2026-10-07 12 ### Added 13 * Added configurable masking for the request, the response and the request arguments, replacing the hardcoded Authorization header and `username`/`password` handling. Rules are declared as `$request_fields_to_mask`, `$response_fields_to_mask` and `$argument_fields_to_mask`, or passed as the `$masked_fields` constructor argument, where they merge into what the class already declares. 14 * Added an allow list to the masking configuration. The reserved `keep` key masks every key in a container that it does not name, so a field the provider adds later is masked by default. 15 * Added `Krokedil\WpApi\FieldMasker`, so a plugin that logs from outside a `Request` subclass can apply the same configured rules. 16 * Added `mask_request_url()` for APIs that address a resource by a token in the path. It returns the URL unchanged by default, and an override that throws costs the log line and not the API call. 17 * Added PHPUnit, PHPCS and PHPStan with `composer test`, `composer phpcs` and `composer phpstan`, and GitHub Actions workflows for pull requests. 18 * Added XML support for response bodies. `get_response_body()` parses the body by its content type: JSON is decoded, XML is parsed into an array, HTML is returned as a string. A body with no or an unknown content type is decoded as JSON when it is valid JSON, so the response masking rules still reach into it. 19 * Added log levels. `Logger::log()` writes with the WooCommerce logger at the `log_level` of the entry, `error` for a non 2xx response and for a body `status` of `error`, `warning` for a body `status` of `warning`, otherwise `info`. 20 * `WP_Error` responses from `wp_remote_request()` are now logged. 21 * Added the `krokedil_wp_api_mask_log_data` filter. Return `false` to turn the log masking off while debugging. It gets the plugin slug as its second argument. There is deliberately no setting for it. 22 * Added `Krokedil\WpApi\Masking`, with `mask_fields()`, `mask_keys()` and `is_enabled()`, so a plugin that masks data outside a `Request` subclass honours the filter too. 23 24 ### Changed 25 * `[MISSING]` now tells an empty value from a sent one on every masked field, rather than only on the Authorization header. 26 * Masking no longer decides by length whether a header holds a token. Any value a rule names is replaced, whatever its length. 27 * Data nested deeper than twelve levels is masked rather than walked. 28 * `request()` returns the body parsed by its content type rather than always through `json_decode()`. A 2xx HTML body, or a body with no or an unknown content type that is not JSON, is now returned as a string where it used to be `null`, and a body labelled JSON or XML that cannot be parsed returns a `WP_Error`. 29 * Request logs are written at the `info` level rather than the `notice` default of `WC_Logger::add()`, so a store with its log threshold at `notice` or higher no longer records successful requests. 30 31 ### Deprecated 32 * `sanitize_request_args()` is deprecated and delegates to the configured masking, keeping its behaviour. 33 34 ### Security 35 * The response body is now masked. It was logged in full, so every field the provider returned, customer details included, reached the log. 36 * Stack trace arguments are now masked. With extended debugging on, every value passed to every caller was json encoded into the log as it was. Frame arguments are positional, so a bare token with no key name still survives unless its shape gives it away. 37 * Added a key name pass over the finished log entry. `Krokedil\WpApi\KeyMasker` masks by key name wherever it appears, and by shape for an Authorization value, a JWT and a standalone base64 blob, catching what no rule describes. Consuming plugins widen the list with `KeyMasker::add_keys()`. 38 * Masking fails closed. A section that cannot be masked is logged as `[MASKING FAILED]` rather than in the clear. 9 39 10 40 ------------------ -
klarna-payments-for-woocommerce/trunk/dependencies/krokedil/wp-api/src/Logger.php
r3150014 r3740915 8 8 namespace KrokedilKlarnaPaymentsDeps\Krokedil\WpApi; 9 9 10 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 11 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Masking; 10 12 /** 11 13 * Logger class for the api requests. Used the WooCommerce logger. … … 14 16 { 15 17 /** 18 * The log levels the WooCommerce logger accepts. 19 */ 20 const LOG_LEVELS = array('emergency', 'alert', 'critical', 'error', 'warning', 'notice', 'info', 'debug'); 21 /** 16 22 * WC Logger instance. 17 23 * 18 * @var \WC_Logger $log24 * @var \WC_Logger_Interface|null $log 19 25 */ 20 26 public static $log; … … 28 34 public static function log($slug, $log_data = array()) 29 35 { 30 // Log the data.31 36 if (empty(self::$log)) { 32 self::$log = new \WC_Logger();37 self::$log = wc_get_logger(); 33 38 } 34 self::$log->add($slug, wp_json_encode($log_data)); 39 // Read the level before masking, a failed pass replaces the whole entry. 40 $log_level = $log_data['log_level'] ?? 'info'; 41 if (!\in_array($log_level, self::LOG_LEVELS, \true)) { 42 $log_level = 'info'; 43 } 44 // A failure here costs the entry, it never lets an unmasked one through. 45 if (Masking::is_enabled($slug)) { 46 try { 47 $log_data = static::mask_log_data($log_data); 48 } catch (\Throwable $e) { 49 $log_data = array('error' => KeyMasker::FAILED); 50 } 51 } 52 self::$log->log($log_level, wp_json_encode($log_data), array('source' => $slug)); 53 } 54 /** 55 * The last pass over the finished entry, catching anything no rule described. 56 * 57 * @param array $log_data The data to log. 58 * @return mixed 59 */ 60 protected static function mask_log_data($log_data) 61 { 62 return KeyMasker::mask($log_data); 35 63 } 36 64 /** … … 38 66 * 39 67 * @param bool $extended_debugging Whether to include the arguments in the stack trace. This should never be turned on by default, but rather only used when extra information is needed. 68 * @param bool $mask Whether to mask the arguments in the stack trace. 40 69 * @return array 41 70 */ 42 public static function get_stack($extended_debugging = \false )71 public static function get_stack($extended_debugging = \false, $mask = \true) 43 72 { 44 73 $debug_data = \debug_backtrace(); … … 48 77 $count = \count($debug_data); 49 78 for ($i = 5; $i < $count; $i++) { 50 self::process_debug_line($stack, $debug_data[$i], $extended_debugging );79 self::process_debug_line($stack, $debug_data[$i], $extended_debugging, $mask); 51 80 } 52 81 return $stack; … … 58 87 * @param array $debug_line The debug info from the raw stack trace. 59 88 * @param bool $extended_debugging Whether to include the arguments in the stack trace. 89 * @param bool $mask Whether to mask the arguments. 60 90 * @return void 61 91 */ 62 private static function process_debug_line(&$stack, $debug_line, $extended_debugging )92 private static function process_debug_line(&$stack, $debug_line, $extended_debugging, $mask = \true) 63 93 { 64 94 $class = $debug_line['class'] ?? ''; … … 68 98 self::handle_wp_hook($class, $function, $args, $debug_line); 69 99 // Construct a caller string. 70 $caller = self::get_caller_string($class, $type, $function, $args, $extended_debugging );100 $caller = self::get_caller_string($class, $type, $function, $args, $extended_debugging, $mask); 71 101 $row = array('file' => $debug_line['file'] ?? '', 'line' => $debug_line['line'] ?? '', 'function' => $caller); 72 102 $stack[] = $row; 73 103 } 74 104 /** 75 * Get the caller string from the stack trace line. 105 * Get the caller string from the stack trace line. Argument values are masked first, but 106 * frame arguments are positional, so there is no key name to match on: a bare token passed 107 * as an argument survives unless its own shape gives it away. Treat these logs as sensitive. 76 108 * 77 109 * @param string $class The class name. … … 80 112 * @param array $args The arguments passed to the caller. 81 113 * @param bool $extended_debugging Whether to include the arguments in the stack trace. 114 * @param bool $mask Whether to mask the arguments. 82 115 * @return string 83 116 */ 84 private static function get_caller_string($class, $type, $function, $args, $extended_debugging )117 private static function get_caller_string($class, $type, $function, $args, $extended_debugging, $mask = \true) 85 118 { 86 119 // Construct a caller string. … … 88 121 $caller .= '('; 89 122 // Only add data if we are doing extended debugging. 90 $caller .= $extended_debugging ? \implode(', ', \array_map(function ($value) { 123 $caller .= $extended_debugging ? \implode(', ', \array_map(function ($value) use($mask) { 124 try { 125 $value = $mask ? KeyMasker::mask($value) : $value; 126 } catch (\Throwable $e) { 127 $value = KeyMasker::FAILED; 128 } 91 129 // Json encode all values so that we can see what objects and arrays are passed. Dont escape anything, partial output on errors, and ignore slashes and line terminators. 92 130 return wp_json_encode($value, \JSON_UNESCAPED_UNICODE | \JSON_PARTIAL_OUTPUT_ON_ERROR | \JSON_UNESCAPED_LINE_TERMINATORS | \JSON_UNESCAPED_SLASHES); -
klarna-payments-for-woocommerce/trunk/dependencies/krokedil/wp-api/src/Request.php
r3150014 r3740915 9 9 10 10 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Logger; 11 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\KeyMasker; 12 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\Masking; 13 use KrokedilKlarnaPaymentsDeps\Krokedil\WpApi\FieldMasker; 11 14 /** 12 15 * Base request class for the package. … … 15 18 { 16 19 /** 20 * The reserved rule key that turns a rule into an allow list. 21 */ 22 const MASK_KEEP = FieldMasker::KEEP; 23 /** 17 24 * Config values. 18 25 * … … 57 64 public $method = 'GET'; 58 65 /** 66 * Fields to mask in the request args before logging. Keys map to a list of field names or 67 * to nested arrays, matched case insensitively wherever the name appears below that level. 68 * The reserved 'keep' key turns a rule into an allow list, masking every key it does not name. 69 * 70 * Example: 71 * array( 72 * 'headers' => array( 'Authorization' ), 73 * 'body' => array( 74 * 'billing_address' => array( 'email', 'phone' ), 75 * 'customer' => array( 'keep' => array( 'type' ) ), 76 * 'attachment' => 'mask', 77 * ), 78 * ) 79 * 80 * @var array 81 */ 82 protected $request_fields_to_mask = array('headers' => array('Authorization')); 83 /** 84 * Fields to mask in the response body before logging. Uses the same structure as the 85 * request fields, including the 'keep' allow list. 86 * 87 * Example: 88 * array( 89 * 'client_token', 90 * 'billing_address' => array( 'email', 'phone' ), 91 * ) 92 * 93 * @var array 94 */ 95 protected $response_fields_to_mask = array(); 96 /** 97 * Fields to mask in the request arguments before logging, same structure as above. 98 * 99 * @var array 100 */ 101 protected $argument_fields_to_mask = array('username', 'password'); 102 /** 59 103 * Constructor. 60 104 * 61 * @param array $config Configuration array. 62 */ 63 public function __construct($config = array(), $settings = array(), $arguments = array()) 105 * @param array $config Configuration array. 106 * @param array $settings Plugin settings. 107 * @param array $arguments Request arguments. 108 * @param array $masked_fields Optional extra fields to mask, with keys 'request', 'response' and/or 'arguments'. 109 * Merged into the configured fields, so an allow list can be passed here as well. 110 */ 111 public function __construct($config = array(), $settings = array(), $arguments = array(), $masked_fields = array()) 64 112 { 65 113 $this->config = wp_parse_args($config, $this->defaults); 66 114 $this->settings = $settings; 67 115 $this->arguments = $arguments; 116 if (!empty($masked_fields['request'])) { 117 $this->request_fields_to_mask = FieldMasker::merge_config($this->request_fields_to_mask, $masked_fields['request']); 118 } 119 if (!empty($masked_fields['response'])) { 120 $this->response_fields_to_mask = FieldMasker::merge_config($this->response_fields_to_mask, $masked_fields['response']); 121 } 122 if (!empty($masked_fields['arguments'])) { 123 $this->argument_fields_to_mask = FieldMasker::merge_config($this->argument_fields_to_mask, $masked_fields['arguments']); 124 } 68 125 } 69 126 /** … … 125 182 { 126 183 if (is_wp_error($response)) { 184 $this->log_response($response, $request_args, $request_url); 127 185 return $response; 128 186 } … … 131 189 $return = $this->get_error_message($response); 132 190 } else { 133 $return = \json_decode(wp_remote_retrieve_body($response), \true);191 $return = $this->get_response_body($response); 134 192 } 135 193 $this->log_response($response, $request_args, $request_url); … … 150 208 } 151 209 // Get the response body if its not a WP_Error. 152 $response_body = !is_wp_error($response) ? \json_decode(wp_remote_retrieve_body($response), \true) : array();210 $response_body = !is_wp_error($response) ? $this->get_response_body($response) : array(); 153 211 $code = wp_remote_retrieve_response_code($response); 154 // Parse the Request body into an array if its json format. 155 $request_body = $request_args['body'] ?? ''; 156 $decoded_body = \json_decode($request_body); 157 $request_args['body'] = $decoded_body ?? $request_args['body'] ?? null; 158 $request_args = $this->sanitize_request_args($request_args); 159 $arguments = $this->arguments; 160 if (isset($arguments['username'])) { 161 $arguments['username'] = '[REDACTED]'; 162 } 163 if (isset($arguments['password'])) { 164 $arguments['password'] = '[REDACTED]'; 212 // Set log level. Read from the body before it is masked. A body that failed to parse is a WP_Error. 213 $body_status = \is_array($response_body) ? $response_body['status'] ?? null : null; 214 if ($code < 200 || $code > 299 || is_wp_error($response_body)) { 215 $log_level = 'error'; 216 } elseif ('error' === $body_status) { 217 // Some APIs (e.g. Fraktjakt) return a successful HTTP status while reporting an 218 // application-level error in the response body. Treat those as errors too. 219 $log_level = 'error'; 220 } elseif ('warning' === $body_status) { 221 $log_level = 'warning'; 222 } else { 223 $log_level = 'info'; 224 } 225 $mask = Masking::is_enabled($this->config['slug']); 226 if ($mask) { 227 $response_body = $this->mask_response($response_body); 228 $arguments = $this->mask_arguments($this->arguments); 229 $request_url = $this->get_masked_request_url($request_url); 230 $request_args = $this->mask_request_args($request_args); 231 } else { 232 $arguments = $this->arguments; 233 if (isset($request_args['body']) && \is_string($request_args['body'])) { 234 $request_args['body'] = $this->decode_body($request_args['body']); 235 } 165 236 } 166 237 // Log the response. … … 172 243 'request_url' => $request_url, 173 244 'response' => array('body' => $response_body, 'code' => $code), 245 'log_level' => $log_level, 174 246 'timestamp' => \date('Y-m-d H:i:s'), 175 247 // phpcs:ignore WordPress.DateTime.RestrictedFunctions -- Date is not used for display. 176 'stack' => Logger::get_stack($this->config['extended_debugging'] ),248 'stack' => Logger::get_stack($this->config['extended_debugging'], $mask), 177 249 'plugin_version' => $this->config['plugin_version'], 178 250 )); 179 251 } 180 252 /** 253 * Returns the body of the response based on the content type. 254 * 255 * @param array|\WP_Error $response The response from wp_remote_get() or wp_remote_post(). 256 * @return array|object|string|\WP_Error Parsed response body or WP_Error if an error occurs. 257 */ 258 public function get_response_body($response) 259 { 260 if (is_wp_error($response)) { 261 return $response; 262 } 263 $content_type = wp_remote_retrieve_header($response, 'content-type'); 264 $content_type = \is_array($content_type) ? \implode(', ', $content_type) : (string) $content_type; 265 $body = wp_remote_retrieve_body($response); 266 if (\strpos($content_type, 'application/json') !== \false) { 267 $parsed_body = \json_decode($body, \true); 268 if (\json_last_error() === \JSON_ERROR_NONE) { 269 return $parsed_body; 270 } 271 return new \WP_Error('json_decode_error', 'Failed to decode JSON'); 272 } elseif (\strpos($content_type, 'text/html') !== \false) { 273 return $body; 274 } elseif (\strpos($content_type, 'application/xml') !== \false || \strpos($content_type, 'text/xml') !== \false) { 275 // Collect parse errors rather than emitting a PHP warning per malformed node. 276 $use_errors = \libxml_use_internal_errors(\true); 277 $parsed_body = \simplexml_load_string($body); 278 \libxml_clear_errors(); 279 \libxml_use_internal_errors($use_errors); 280 if (\false !== $parsed_body) { 281 return \json_decode(wp_json_encode($parsed_body), \true); 282 } 283 return new \WP_Error('xml_parse_error', 'Failed to parse XML'); 284 } else { 285 // No or an unknown content type. Decode json anyway, so a body the API did not label 286 // is still returned as an array and the response masking rules can reach into it. 287 $parsed_body = \json_decode($body, \true); 288 return \is_array($parsed_body) ? $parsed_body : $body; 289 } 290 } 291 /** 292 * Mask sensitive fields in the request args before logging. Reading the configuration 293 * happens inside the guard, so a broken config costs the section and not the request. 294 * 295 * @param array $request_args The request args. 296 * @return array|string The masked request args, or the failure marker. 297 */ 298 protected function mask_request_args($request_args) 299 { 300 try { 301 // Decode the body that was really sent, so the rules can reach into it and the log 302 // holds a readable structure rather than an escaped string. A body no rule can reach 303 // into is masked whole by a configured body rule, see FieldMasker. 304 if (isset($request_args['body']) && \is_string($request_args['body'])) { 305 $request_args['body'] = $this->decode_body($request_args['body']); 306 } 307 $fields = $this->request_fields_to_mask; 308 return empty($fields) ? $request_args : $this->sanitize_field($request_args, $fields); 309 } catch (\Throwable $e) { 310 return KeyMasker::FAILED; 311 } 312 } 313 /** 314 * Decode a json or form encoded body string into an array, or return it as it was. 315 * 316 * @param string $body The body string. 317 * @return array|string 318 */ 319 private function decode_body($body) 320 { 321 $decoded = \json_decode($body, \true); 322 if (\is_array($decoded)) { 323 return $decoded; 324 } 325 // What http_build_query() produces: key=value pairs joined by '&', nothing else. 326 if (\preg_match('/^[^=&\\s]+=[^&\\s]*(?:&[^=&\\s]+=[^&\\s]*)*$/', $body)) { 327 \parse_str($body, $decoded); 328 return $decoded; 329 } 330 return $body; 331 } 332 /** 333 * Mask sensitive fields in the response body before logging. 334 * 335 * @param array|\WP_Error $response The decoded response body. 336 * @return array|string|\WP_Error The masked response body, or the failure marker. 337 */ 338 protected function mask_response($response) 339 { 340 if (is_wp_error($response) || empty($response)) { 341 return $response; 342 } 343 try { 344 $fields = $this->response_fields_to_mask; 345 return empty($fields) ? $response : $this->sanitize_field($response, $fields); 346 } catch (\Throwable $e) { 347 return KeyMasker::FAILED; 348 } 349 } 350 /** 351 * Mask sensitive fields in the request arguments before logging. 352 * 353 * @param array $arguments The request arguments. 354 * @return array|string The masked arguments, or the failure marker. 355 */ 356 protected function mask_arguments($arguments) 357 { 358 try { 359 $fields = $this->argument_fields_to_mask; 360 return empty($fields) ? $arguments : $this->sanitize_field($arguments, $fields); 361 } catch (\Throwable $e) { 362 return KeyMasker::FAILED; 363 } 364 } 365 /** 366 * Mask the request URL before logging, unchanged by default. Override it when the API 367 * addresses a resource by a token in the path, such as /authorizations/{token}/order. 368 * 369 * @param string $request_url The request URL. 370 * @return string 371 */ 372 protected function mask_request_url($request_url) 373 { 374 return $request_url; 375 } 376 /** 377 * Call mask_request_url() without letting an override that throws kill the API call. 378 * The fallback is a redaction, never the raw URL. 379 * 380 * @param string $request_url The request URL. 381 * @return string 382 */ 383 private function get_masked_request_url($request_url) 384 { 385 try { 386 $masked = $this->mask_request_url($request_url); 387 return \is_string($masked) ? $masked : KeyMasker::FAILED; 388 } catch (\Throwable $e) { 389 return KeyMasker::FAILED; 390 } 391 } 392 /** 393 * Recursively mask a set of fields within a data array. A rule keeps matching below the 394 * level it was declared on, so a container nested in a list is treated like one at the root. 395 * 396 * @param array $data The data array to sanitize. 397 * @param array $fields_to_sanitize The fields to sanitize. 398 * @return array 399 */ 400 protected function sanitize_field($data, $fields_to_sanitize) 401 { 402 return FieldMasker::mask($data, $fields_to_sanitize); 403 } 404 /** 181 405 * Remove sensitive data from the log. 182 406 * 407 * @deprecated Use mask_request_args() instead. Kept for backward compatibility. 183 408 * @param array $request_args The request data to sanitize. 184 * @return array The request data sanitized.409 * @return array|string The request data sanitized, or the failure marker. 185 410 */ 186 411 protected function sanitize_request_args($request_args) 187 412 { 188 // Do not log the authorization token. 189 foreach ($request_args['headers'] as $header => $value) { 190 if ('authorization' === \strtolower($header)) { 191 // If it is longer than 15 char., it most likely has a token. This is an assumption that is safe even if it is wrong. 192 $request_args['headers'][$header] = \strlen($value) > 15 ? '[REDACTED]' : '[MISSING]'; 193 break; 194 } 195 } 196 return $request_args; 413 return $this->mask_request_args($request_args); 197 414 } 198 415 /** -
klarna-payments-for-woocommerce/trunk/klarna-payments-for-woocommerce.php
r3728366 r3740915 6 6 * Author: klarna 7 7 * Author URI: https://www.klarna.com/ 8 * Version: 4.13. 38 * Version: 4.13.4 9 9 * Text Domain: klarna-payments-for-woocommerce 10 10 * Domain Path: /languages 11 11 * 12 12 * WC requires at least: 5.6.0 13 * WC tested up to: 11. 1.213 * WC tested up to: 11.2.1 14 14 * Requires Plugins: woocommerce 15 15 * … … 38 38 use Krokedil\Klarna\ExpressCheckout; 39 39 use Krokedil\Klarna\Utilities\ApiCredentialsUtility; 40 use Krokedil\Klarna\Logging\LogMasking; 41 use Krokedil\Klarna\Logging\LogWriter; 40 42 41 43 if ( ! defined( 'ABSPATH' ) ) { … … 53 55 * Required minimums and constants 54 56 */ 55 define( 'WC_KLARNA_PAYMENTS_VERSION', '4.13. 3' );57 define( 'WC_KLARNA_PAYMENTS_VERSION', '4.13.4' ); 56 58 define( 'WC_KLARNA_PAYMENTS_MIN_PHP_VER', '7.4.0' ); 57 59 define( 'WC_KLARNA_PAYMENTS_MIN_WC_VER', '5.6.0' ); … … 331 333 $this->order_management = new OrderManagement(); 332 334 $this->logger = new Logger( 'klarna_payments', 'no' !== ( $settings['logging'] ?? 'no' ) ); 335 336 // Masking has to be in place before anything can log. 337 LogMasking::register(); 338 LogWriter::register(); 333 339 Compatibility::register(); 334 340 -
klarna-payments-for-woocommerce/trunk/languages/klarna-payments-for-woocommerce.pot
r3728366 r3740915 6 6 "Report-Msgid-Bugs-To: " 7 7 "https://wordpress.org/support/plugin/klarna-payments-for-woocommerce\n" 8 "POT-Creation-Date: 2026-10- 05 08:10:20+00:00\n"8 "POT-Creation-Date: 2026-10-12 09:04:01+00:00\n" 9 9 "MIME-Version: 1.0\n" 10 10 "Content-Type: text/plain; charset=utf-8\n" … … 513 513 msgstr "" 514 514 515 #: classes/class-kp-assets.php:2 50515 #: classes/class-kp-assets.php:283 516 516 #. translators: [customer-facing]. 517 517 msgid "Please read and accept the terms and conditions to proceed with your order." 518 518 msgstr "" 519 519 520 #: classes/class-kp-assets.php:3 01520 #: classes/class-kp-assets.php:334 521 521 #. translators: [merchant-facing]. 522 522 msgid "Select all" … … 728 728 #: dependencies/krokedil/settings-page/src/Navigation.php:54 729 729 #: dependencies/krokedil/settings-page/src/Support.php:36 730 #: klarna-payments-for-woocommerce.php:4 49730 #: klarna-payments-for-woocommerce.php:455 731 731 #. translators: [merchant-facing]. 732 732 msgid "Support" … … 879 879 msgstr "" 880 880 881 #: includes/kp-functions.php:738 klarna-payments-for-woocommerce.php:6 65881 #: includes/kp-functions.php:738 klarna-payments-for-woocommerce.php:671 882 882 #. translators: [customer-facing]. 883 883 msgid "Pay with Klarna" 884 884 msgstr "" 885 885 886 #: klarna-payments-for-woocommerce.php:4 45886 #: klarna-payments-for-woocommerce.php:451 887 887 #. translators: [merchant-facing]. 888 888 msgid "Settings" 889 889 msgstr "" 890 890 891 #: klarna-payments-for-woocommerce.php:4 47891 #: klarna-payments-for-woocommerce.php:453 892 892 #. translators: [merchant-facing]. 893 893 msgid "Docs" 894 894 msgstr "" 895 895 896 #: klarna-payments-for-woocommerce.php:49 1896 #: klarna-payments-for-woocommerce.php:497 897 897 #. translators: [merchant-facing]. 898 898 msgid "The WooCommerce plugin must be active for Klarna Payments to work." 899 899 msgstr "" 900 900 901 #: klarna-payments-for-woocommerce.php:51 0901 #: klarna-payments-for-woocommerce.php:516 902 902 #. translators: [merchant-facing]. 903 903 msgid "Dismiss" 904 904 msgstr "" 905 905 906 #: klarna-payments-for-woocommerce.php:5 17906 #: klarna-payments-for-woocommerce.php:523 907 907 #. translators: [merchant-facing]. URL to docs. 908 908 msgid "" … … 912 912 msgstr "" 913 913 914 #: klarna-payments-for-woocommerce.php:61 1915 #: klarna-payments-for-woocommerce.php:62 0914 #: klarna-payments-for-woocommerce.php:617 915 #: klarna-payments-for-woocommerce.php:626 916 916 msgid "" 917 917 "Your installation of Klarna Payments is not complete. If you installed this " … … 920 920 msgstr "" 921 921 922 #: klarna-payments-for-woocommerce.php:6 55922 #: klarna-payments-for-woocommerce.php:661 923 923 #. translators: [customer-facing]. 924 924 msgid "Pay with Klarna (free)" … … 942 942 943 943 #: src/ExpressCheckout/Api/Notifications/PaymentStateExpired.php:43 944 #: src/ExpressCheckout/KECOneStepIntegration.php:10 1944 #: src/ExpressCheckout/KECOneStepIntegration.php:102 945 945 #. translators: [merchant-facing]. 946 946 msgid "Order cancelled due to expired payment request." … … 1478 1478 msgstr "" 1479 1479 1480 #: src/OrderManagement/Request/Request.php:16 41480 #: src/OrderManagement/Request/Request.php:165 1481 1481 #. translators: [merchant-facing]. 1482 1482 msgid "Invalid JSON response from the server." -
klarna-payments-for-woocommerce/trunk/readme.txt
r3728366 r3740915 7 7 Requires PHP: 8.0 8 8 WC requires at least: 5.6.0 9 WC tested up to: 11. 1.210 Stable tag: 4.13. 39 WC tested up to: 11.2.1 10 Stable tag: 4.13.4 11 11 License: GPLv3 or later 12 12 License URI: http://www.gnu.org/licenses/gpl-3.0.html … … 70 70 71 71 == Changelog == 72 = 2026-10-12 - version 4.13.4 = 73 * Enhancement - Masked customer personal, and other sensitive data and credentials in the plugin log. 74 * Fix - Order details are now only added to the pay for order page when WooCommerce would show the payment form: the request carries the order's key, the customer may pay for the order, the order still needs payment, and a guest has confirmed the billing email. 75 72 76 = 2026-10-05 - version 4.13.3 = 73 77 * Enhancement - Improved handling of Klarna authorization callbacks and background processing. -
klarna-payments-for-woocommerce/trunk/src/ExpressCheckout/KECOneStepIntegration.php
r3716655 r3740915 94 94 95 95 if ( ! $payment_request_id || ! $interoperability_token ) { 96 KP_WC()->logger()->error( '[KEC One Step] Missing required fields in payload for expired payment: ' . wp_json_encode( $payload ) ); 96 $missing = $payment_request_id ? 'interoperability_token' : 'payment_request_id'; 97 KP_WC()->logger()->error( "[KEC One Step] Missing {$missing} in the payload for expired payment on order {$order->get_id()}." ); 97 98 return; 98 99 } -
klarna-payments-for-woocommerce/trunk/src/OrderManagement/Request/Request.php
r3664907 r3740915 3 3 4 4 use Krokedil\Klarna\OrderManagement; 5 use Krokedil\Klarna\Logging\LogMasking; 5 6 use Krokedil\Klarna\Utilities\ApiCredentialsUtility; 6 7 … … 355 356 */ 356 357 protected function log_response( $response, $request_args, $request_url, $code ) { 357 foreach ( $request_args['headers'] as $header => $value ) {358 if ( 'authorization' === strtolower( $header ) ) {359 // If it is longer than 15 char., it most likely has a token. This is an assumption that is safe even if it is wrong.360 $request_args['headers'][ $header ] = strlen( $value ) > 15 ? '[REDACTED]' : '[MISSING]'; 361 break;362 }363 } 364 $log = \KP_Logger::format_log( $this->klarna_order_id, $this->method, $this->log_title, $request_args, $ response, $code, $request_url );358 // Decode the body so the masking can reach into it, and drop the rest of the HTTP 359 // response, which holds nothing a log needs and hides the payload inside a string. 360 $body = is_wp_error( $response ) ? array() : json_decode( wp_remote_retrieve_body( $response ), true ); 361 362 $request_args = LogMasking::mask_request( $request_args ); 363 $body = LogMasking::mask_response( $body ); 364 365 $log = \KP_Logger::format_log( $this->klarna_order_id, $this->method, $this->log_title, $request_args, $body, $code, $request_url ); 365 366 \KP_Logger::log( $log ); 366 367 } -
klarna-payments-for-woocommerce/trunk/vendor/composer/autoload_classmap.php
r3664907 r3740915 44 44 'Krokedil\\Klarna\\ExpressCheckout\\WebhookSetup' => $baseDir . '/src/ExpressCheckout/WebhookSetup.php', 45 45 'Krokedil\\Klarna\\Features' => $baseDir . '/src/Features.php', 46 'Krokedil\\Klarna\\Logging\\LogMasking' => $baseDir . '/src/Logging/LogMasking.php', 47 'Krokedil\\Klarna\\Logging\\LogWriter' => $baseDir . '/src/Logging/LogWriter.php', 46 48 'Krokedil\\Klarna\\OnsiteMessaging\\Blocks\\CartBlockIntegration' => $baseDir . '/src/OnsiteMessaging/Blocks/CartBlockIntegration.php', 47 49 'Krokedil\\Klarna\\OnsiteMessaging\\KlarnaOnsiteMessaging' => $baseDir . '/src/OnsiteMessaging/KlarnaOnsiteMessaging.php', -
klarna-payments-for-woocommerce/trunk/vendor/composer/autoload_static.php
r3696456 r3740915 67 67 'Krokedil\\Klarna\\ExpressCheckout\\WebhookSetup' => __DIR__ . '/../..' . '/src/ExpressCheckout/WebhookSetup.php', 68 68 'Krokedil\\Klarna\\Features' => __DIR__ . '/../..' . '/src/Features.php', 69 'Krokedil\\Klarna\\Logging\\LogMasking' => __DIR__ . '/../..' . '/src/Logging/LogMasking.php', 70 'Krokedil\\Klarna\\Logging\\LogWriter' => __DIR__ . '/../..' . '/src/Logging/LogWriter.php', 69 71 'Krokedil\\Klarna\\OnsiteMessaging\\Blocks\\CartBlockIntegration' => __DIR__ . '/../..' . '/src/OnsiteMessaging/Blocks/CartBlockIntegration.php', 70 72 'Krokedil\\Klarna\\OnsiteMessaging\\KlarnaOnsiteMessaging' => __DIR__ . '/../..' . '/src/OnsiteMessaging/KlarnaOnsiteMessaging.php', -
klarna-payments-for-woocommerce/trunk/vendor/composer/installed.php
r3728366 r3740915 2 2 'root' => array( 3 3 'name' => '__root__', 4 'pretty_version' => '4.13. 3',5 'version' => '4.13. 3.0',6 'reference' => ' 0d6c8537e224d077da2c9b85736448423cc02baf',4 'pretty_version' => '4.13.4', 5 'version' => '4.13.4.0', 6 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 7 7 'type' => 'library', 8 8 'install_path' => __DIR__ . '/../../', … … 12 12 'versions' => array( 13 13 '__root__' => array( 14 'pretty_version' => '4.13. 3',15 'version' => '4.13. 3.0',16 'reference' => ' 0d6c8537e224d077da2c9b85736448423cc02baf',14 'pretty_version' => '4.13.4', 15 'version' => '4.13.4.0', 16 'reference' => '5eda0fcd3d6d0288827dc8b76adf3ae1e2d5133e', 17 17 'type' => 'library', 18 18 'install_path' => __DIR__ . '/../../',
Note:
See TracChangeset
for help on using the changeset viewer.
![(please configure the [header_logo] section in trac.ini)](/chrome/site/your_project_logo.png)