diff --git a/.distignore b/.distignore new file mode 100644 index 0000000..9a90621 --- /dev/null +++ b/.distignore @@ -0,0 +1,43 @@ +# Files and directories that must not ship inside the plugin zip. +# `@wordpress/scripts plugin-zip` and wordpress.org's distribution tools +# respect these ignore rules. + +# VCS and editor metadata +.git +.github +.gitignore +.gitattributes +.idea +.vscode +.DS_Store + +# Node / build tooling +node_modules +package.json +package-lock.json +yarn.lock +webpack.config.js +.wp-env.json + +# Source (only build/ ships) +src + +# Tests / CI +tests +phpunit.xml +phpunit.xml.dist +.phpcs.xml +.phpcs.xml.dist +phpcs.xml +phpcs.xml.dist + +# Local artifacts +.playwright-mcp +*.png +*.log +*.map + +# Markdown readme (readme.txt is the canonical one) +readme.md +README.md +CHANGELOG.md diff --git a/package-lock.json b/package-lock.json index 6c9cb1f..59054c3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,11 +1,11 @@ { - "name": "openid4vp-plugin", + "name": "universal-openid4vp-plugin", "version": "0.7.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "openid4vp-plugin", + "name": "universal-openid4vp-plugin", "version": "0.7.0", "license": "GPL-2.0-or-later", "devDependencies": { diff --git a/readme.txt b/readme.txt index 9d557e1..2733d97 100644 --- a/readme.txt +++ b/readme.txt @@ -1,58 +1,44 @@ -=== Copyright Date Block === -Contributors: Credenco B.V. -Tags: block -Tested up to: 6.6 +=== Universal OID4VP === +Contributors: credenco +Tags: openid4vp, verifiable-credentials, sso, login, identity +Requires at least: 6.6 +Tested up to: 6.9 +Requires PHP: 7.4 Stable tag: 0.7.0 License: GPL-2.0-or-later License URI: https://www.gnu.org/licenses/gpl-2.0.html -Display your site's copyright date. +Retrieve verifiable presentations from a personal or business wallet using the OpenID for Verifiable Presentations (OpenID4VP) flow. == Description == -This is the long description. No limit, and you can use Markdown (as well as in the following sections). +Universal OID4VP lets a WordPress site request and consume verifiable credentials from a user's digital wallet over the OpenID for Verifiable Presentations (OpenID4VP) protocol. -For backwards compatibility, if this section is missing, the full length of the short description will be used, and -Markdown parsed. +The plugin adds: -== Installation == - -This section describes how to install the plugin and get it working. - -e.g. - -1. Upload the plugin files to the `/wp-content/plugins/openid4vp-block` directory, or install the plugin through the WordPress plugins screen directly. -1. Activate the plugin through the 'Plugins' screen in WordPress - - -== Frequently Asked Questions == +* A settings page where the site administrator configures the OpenID4VP and token endpoints, API client credentials, and optional wallet-based login behaviour. +* A block that starts a presentation exchange with a personal wallet (QR code or link). +* A block that starts a presentation exchange with a business wallet. +* A block that displays an attribute obtained from a received presentation. +* An optional "Login with Personal Wallet" button on the standard WordPress login form. -= A question that someone might have = +== External services == -An answer to that question. +This plugin communicates with an OpenID4VP backend that you configure in the plugin settings (for example `https://wallet.acc.credenco.com`) and with a token endpoint (for example a Keycloak `/protocol/openid-connect/token` URL). Both URLs are chosen by the site administrator. No data is sent to those services until a visitor actively uses a wallet block or the wallet login button. -= What about foo bar? = +The data exchanged with the configured endpoints includes the API client credentials entered on the settings page, the query identifier configured on the block, and — for responses — the verifiable presentation returned by the wallet. -Answer to foo bar dilemma. - -== Screenshots == +== Installation == -1. This screen shot description corresponds to screenshot-1.(png|jpg|jpeg|gif). Note that the screenshot is taken from -the /assets directory or the directory that contains the stable readme.txt (tags or trunk). Screenshots in the /assets -directory take precedence. For example, `/assets/screenshot-1.png` would win over `/tags/4.3/screenshot-1.png` -(or jpg, jpeg, gif). -2. This is the second screen shot +1. Upload the plugin zip via the "Plugins" screen in WordPress, or extract it to `/wp-content/plugins/universal-openid4vp-plugin`. +2. Activate the plugin through the "Plugins" screen. +3. Go to Settings -> Universal OID4VP and fill in the OpenID4VP endpoint, token endpoint, API client id and secret. +4. Add one of the Universal OID4VP blocks to a page to start a presentation exchange. == Changelog == -= 0.1.0 = -* Release - -= 0.2.0 = -* Release - -== Arbitrary section == += 0.7.0 = +* Maintenance release: hardening for wordpress.org submission (input sanitization, output escaping, nonces on AJAX endpoints, readme). -You may provide arbitrary sections, in the same format as the ones above. This may be of use for extremely complicated -plugins where more information needs to be conveyed that doesn't fit into the categories of "description" or -"installation." Arbitrary sections will be shown below the built-in sections outlined above. += 0.1.0 = +* Initial release. diff --git a/src/OpenID4VP.php b/src/OpenID4VP.php index 7f7f935..2dac561 100644 --- a/src/OpenID4VP.php +++ b/src/OpenID4VP.php @@ -45,8 +45,8 @@ public function setup() { } public function logout() { - wp_redirect(home_url()); - exit(); + wp_safe_redirect( home_url() ); + exit; } public function wp_enqueue() { diff --git a/src/adminSettings/openid4vp-admin-settings.php b/src/adminSettings/openid4vp-admin-settings.php index a423e97..0a6524f 100644 --- a/src/adminSettings/openid4vp-admin-settings.php +++ b/src/adminSettings/openid4vp-admin-settings.php @@ -114,7 +114,7 @@ public function universal_openid4vp_create_settings_page() {

- +

diff --git a/src/openid4vp-session.php b/src/openid4vp-session.php new file mode 100644 index 0000000..d3668f7 --- /dev/null +++ b/src/openid4vp-session.php @@ -0,0 +1,96 @@ + 0, + 'path' => defined( 'COOKIEPATH' ) && COOKIEPATH ? COOKIEPATH : '/', + 'domain' => defined( 'COOKIE_DOMAIN' ) ? COOKIE_DOMAIN : '', + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); +} +add_action( 'init', 'universal_openid4vp_session_bootstrap', 0 ); + +function universal_openid4vp_session_token() { + if ( empty( $_COOKIE[ UNIVERSAL_OPENID4VP_COOKIE_NAME ] ) ) { + return ''; + } + $token = sanitize_text_field( wp_unslash( $_COOKIE[ UNIVERSAL_OPENID4VP_COOKIE_NAME ] ) ); + if ( ! preg_match( '/^[A-Za-z0-9]{16,64}$/', $token ) ) { + return ''; + } + return $token; +} + +function universal_openid4vp_session_key() { + $token = universal_openid4vp_session_token(); + return $token ? UNIVERSAL_OPENID4VP_TRANSIENT_PREFIX . $token : ''; +} + +function universal_openid4vp_session_get_all() { + $key = universal_openid4vp_session_key(); + if ( empty( $key ) ) { + return array(); + } + $data = get_transient( $key ); + return is_array( $data ) ? $data : array(); +} + +function universal_openid4vp_session_get( $name, $default_value = null ) { + $data = universal_openid4vp_session_get_all(); + return isset( $data[ $name ] ) ? $data[ $name ] : $default_value; +} + +function universal_openid4vp_session_set( $name, $value ) { + $key = universal_openid4vp_session_key(); + if ( empty( $key ) ) { + return false; + } + $data = universal_openid4vp_session_get_all(); + $data[ $name ] = $value; + return set_transient( $key, $data, UNIVERSAL_OPENID4VP_SESSION_TTL ); +} + +function universal_openid4vp_session_delete( $name ) { + $key = universal_openid4vp_session_key(); + if ( empty( $key ) ) { + return false; + } + $data = universal_openid4vp_session_get_all(); + unset( $data[ $name ] ); + return set_transient( $key, $data, UNIVERSAL_OPENID4VP_SESSION_TTL ); +} diff --git a/src/presentationAttribute/block.json b/src/presentationAttribute/block.json index edfd1e1..a3d0c2a 100644 --- a/src/presentationAttribute/block.json +++ b/src/presentationAttribute/block.json @@ -28,7 +28,7 @@ "fontSize": true } }, - "textdomain": "openid4vp-attribute", + "textdomain": "universal-openid4vp", "editorScript": "file:./index.js", "editorStyle": "file:./index.css", "style": "file:./style-index.css", diff --git a/src/presentationAttribute/edit.js b/src/presentationAttribute/edit.js index 59001f1..e863d9f 100644 --- a/src/presentationAttribute/edit.js +++ b/src/presentationAttribute/edit.js @@ -54,11 +54,11 @@ export default function Edit( { attributes, setAttributes } ) { return ( <> - + @@ -68,7 +68,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -78,7 +78,7 @@ export default function Edit( { attributes, setAttributes } ) { diff --git a/src/presentationAttribute/render.php b/src/presentationAttribute/render.php index 3fe2fb2..df1ed19 100644 --- a/src/presentationAttribute/render.php +++ b/src/presentationAttribute/render.php @@ -1,27 +1,18 @@ 'application/json'); - if (isset($_SESSION['accessToken'])) { - $headers['Authorization'] = 'Bearer ' . $_SESSION['accessToken']; + if ( ! empty( $storedAccessToken ) ) { + $headers['Authorization'] = 'Bearer ' . $storedAccessToken; } $response = wp_remote_get( $presentationStatusUri, array( @@ -33,24 +24,24 @@ $body = wp_remote_retrieve_body($response); - error_log('Result: '. $body); - $successUrl = null; $statusResponse = json_decode( $body, true); if ( $statusResponse['status'] === 'authorization_response_verified' ) { $credentialClaims = $statusResponse['verified_data']['credential_claims']; + $storedPresentationResponse = universal_openid4vp_session_get( 'presentationResponse', array() ); + if ( ! is_array( $storedPresentationResponse ) ) { + $storedPresentationResponse = array(); + } foreach ($credentialClaims as $credential) { - if (empty($_SESSION['presentationResponse'])) { - $_SESSION['presentationResponse'] = []; - } - $_SESSION['presentationResponse'][$credential['id']] = $credential; + $storedPresentationResponse[ $credential['id'] ] = $credential; } - $presentationResponse = isset($_SESSION['presentationResponse']) ? $_SESSION['presentationResponse'] : null; + universal_openid4vp_session_set( 'presentationResponse', $storedPresentationResponse ); + $presentationResponse = $storedPresentationResponse; - $_SESSION['accessToken'] = null; - $_SESSION['successUrl'] = null; + universal_openid4vp_session_delete( 'accessToken' ); + universal_openid4vp_session_delete( 'successUrl' ); } } @@ -73,13 +64,12 @@ // $arr is now array(2, 4, 6, 8) unset($name); - $label = trim($attributes['attributeLabel']); - if ($label !== '') { - $block_content = '

' . esc_html($label) . ': ' . esc_html($result) . '

'; - } else { - $block_content = '

' . esc_html($result) . '

'; + $label = isset( $attributes['attributeLabel'] ) ? trim( $attributes['attributeLabel'] ) : ''; + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_block_wrapper_attributes() returns core-sanitized HTML attributes. + echo '

'; + if ( '' !== $label ) { + echo esc_html( $label ) . ': '; } - - echo $block_content; + echo esc_html( $result ) . '

'; } } diff --git a/src/presentationExchange/block.json b/src/presentationExchange/block.json index 60198d0..cb42ccd 100644 --- a/src/presentationExchange/block.json +++ b/src/presentationExchange/block.json @@ -65,7 +65,7 @@ "fontSize": true } }, - "textdomain": "openid4vp-exchange", + "textdomain": "universal-openid4vp", "editorScript": "file:./index.js", "editorStyle": "file:./index.css", "style": "file:./style-index.css", diff --git a/src/presentationExchange/edit.js b/src/presentationExchange/edit.js index d96f016..8237a74 100644 --- a/src/presentationExchange/edit.js +++ b/src/presentationExchange/edit.js @@ -54,11 +54,11 @@ export default function Edit( { attributes, setAttributes } ) { return ( <> - + @@ -68,7 +68,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -81,7 +81,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -113,7 +113,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -123,7 +123,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -133,7 +133,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -143,7 +143,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -153,7 +153,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -181,7 +181,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -192,7 +192,7 @@ export default function Edit( { attributes, setAttributes } ) { { qrCodeEnabled && @@ -202,7 +202,7 @@ export default function Edit( { attributes, setAttributes } ) { { qrCodeEnabled && diff --git a/src/presentationExchange/login.php b/src/presentationExchange/login.php deleted file mode 100644 index d723140..0000000 --- a/src/presentationExchange/login.php +++ /dev/null @@ -1,16 +0,0 @@ -qr_uri . '">or ' : ''; -$block_content = '
' . $qr_content . 'click link
'; +$allowed_protocols = array( 'http', 'https', 'openid4vp', 'haip', 'mdoc-openid4vp', 'eudi-openid4vp' ); -echo $block_content; +// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_block_wrapper_attributes() returns core-sanitized HTML attributes. +echo '
'; +if ( ! empty( $attributes['qrCodeEnabled'] ) ) { + echo 'or '; +} +echo 'click ' . esc_html__( 'link', 'universal-openid4vp' ) . '
'; diff --git a/src/presentationExchange/status.php b/src/presentationExchange/status.php deleted file mode 100644 index d7791f0..0000000 --- a/src/presentationExchange/status.php +++ /dev/null @@ -1,26 +0,0 @@ - - + @@ -68,7 +68,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -81,7 +81,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -113,7 +113,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -123,7 +123,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -133,7 +133,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -143,7 +143,7 @@ export default function Edit( { attributes, setAttributes } ) { @@ -151,12 +151,21 @@ export default function Edit( { attributes, setAttributes } ) { } /> -

+

- - + + +
-

+
); } diff --git a/src/presentationExchangeOrgWallet/render.php b/src/presentationExchangeOrgWallet/render.php index f2cf91e..770af56 100644 --- a/src/presentationExchangeOrgWallet/render.php +++ b/src/presentationExchangeOrgWallet/render.php @@ -1,49 +1,23 @@ openidEndpoint; -$authenticationHeaderName = $options->authenticationHeaderName; -$authenticationToken = $options->authenticationToken; -if (!empty($attributes['openidEndpoint'])) { - $openidEndpoint = $attributes['openidEndpoint']; - $authenticationHeaderName = $attributes['authenticationHeaderName']; - $authenticationToken = $attributes['authenticationToken']; -} - -$_SESSION['openidEndpoint'] = $openidEndpoint; -$_SESSION['authenticationHeaderName'] = $authenticationHeaderName; -$_SESSION['authenticationToken'] = $authenticationToken; -$_SESSION['queryAttributes'] = $attributes; +universal_openid4vp_session_set( 'queryAttributes', $attributes ); -if (array_key_exists('successUrl', $attributes)) { - $_SESSION['successUrl'] = wp_sanitize_redirect($attributes['successUrl']); +if ( array_key_exists( 'successUrl', $attributes ) ) { + universal_openid4vp_session_set( 'successUrl', wp_sanitize_redirect( $attributes['successUrl'] ) ); } -do_action( 'submitPresentationRequest' ); - +universal_openid4vp_enqueue_org_wallet_scripts(); -// Add JavaScript to handle the form submission -universal_openid4vp_enqueue_org_wallet_scripts('jquery'); - -$block_content = '
-
- - -
-
'; - -echo $block_content; +// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- get_block_wrapper_attributes() returns core-sanitized HTML attributes. +echo '
'; +?> +
+ + + +
+'; diff --git a/src/presentationExchangeOrgWallet/submitPresentationRequest.js b/src/presentationExchangeOrgWallet/submitPresentationRequest.js index 0c8ae16..b6827cf 100644 --- a/src/presentationExchangeOrgWallet/submitPresentationRequest.js +++ b/src/presentationExchangeOrgWallet/submitPresentationRequest.js @@ -6,6 +6,7 @@ var data = { action: "universal_openid4vp_presentation_exchange_ajax", + nonce: my_ajax_obj.nonce, walletUrl: walletUrl }; diff --git a/universal-openid4vp-plugin.php b/universal-openid4vp-plugin.php index 483ec76..d76b4ba 100644 --- a/universal-openid4vp-plugin.php +++ b/universal-openid4vp-plugin.php @@ -4,11 +4,11 @@ * Description: Retrieve verifiable presentations * Version: 0.7.0 * Requires at least: 6.6 - * Requires PHP: 7.2 + * Requires PHP: 7.4 * Author: Credenco * License: GPL-2.0-or-later * License URI: https://www.gnu.org/licenses/gpl-2.0.html - * Text Domain: openid4vp-exchange + * Text Domain: universal-openid4vp * * @package create-block */ @@ -23,8 +23,12 @@ if (!defined('UNIVERSAL_OPENID4VP_PLUGIN_DIR')) { define('UNIVERSAL_OPENID4VP_PLUGIN_DIR', trailingslashit(plugin_dir_path(__FILE__))); } +if ( ! defined( 'UNIVERSAL_OPENID4VP_PLUGIN_VERSION' ) ) { + define( 'UNIVERSAL_OPENID4VP_PLUGIN_VERSION', '0.7.0' ); +} require_once(UNIVERSAL_OPENID4VP_PLUGIN_DIR . 'build/OpenID4VP.php'); +require_once(UNIVERSAL_OPENID4VP_PLUGIN_DIR . 'build/openid4vp-session.php'); $openid4vp = new Universal_OpenID4VP(); @@ -45,9 +49,6 @@ function universal_openid4vp_create_block_init() { register_block_type( __DIR__ . '/build/presentationExchange' ); register_block_type( __DIR__ . '/build/presentationExchangeOrgWallet' ); register_block_type( __DIR__ . '/build/presentationAttribute' ); - if(!session_id()) { - session_start(); - } } function universal_openid4vp_login_form_button() { @@ -69,39 +70,46 @@ function universal_openid4vp_login_form_button() { * Enqueues our scripts */ function universal_openid4vp_enqueue_personal_wallet_scripts() { - // Enqueue our script, using the jQuery dependency - wp_enqueue_script( 'pollStatus', UNIVERSAL_OPENID4VP_PLUGIN_URL . '/build/presentationExchange/dummy.js', array( 'jquery' )); + wp_enqueue_script( + 'pollStatus', + UNIVERSAL_OPENID4VP_PLUGIN_URL . '/build/presentationExchange/dummy.js', + array( 'jquery' ), + UNIVERSAL_OPENID4VP_PLUGIN_VERSION, + true + ); wp_localize_script( 'pollStatus', 'my_ajax_obj', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'universal_openid4vp_ajax' ), ) ); } function universal_openid4vp_enqueue_org_wallet_scripts() { - // Enqueue our script, using the jQuery dependency - wp_enqueue_script( 'submitPresentationRequest', UNIVERSAL_OPENID4VP_PLUGIN_URL . '/build/presentationExchange/dummy.js', array( 'jquery' )); + wp_enqueue_script( + 'submitPresentationRequest', + UNIVERSAL_OPENID4VP_PLUGIN_URL . '/build/presentationExchange/dummy.js', + array( 'jquery' ), + UNIVERSAL_OPENID4VP_PLUGIN_VERSION, + true + ); wp_localize_script( 'submitPresentationRequest', 'my_ajax_obj', array( 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'universal_openid4vp_ajax' ), ) ); } -add_action( 'init', function() { - register_block_type( __DIR__, array( - 'script' => array( 'jquery' ) // makes sure jQuery loads - ) ); -} ); add_action( 'init', 'universal_openid4vp_create_block_init' ); // Display the Login button at the top of the WP Login form add_action('login_message', 'universal_openid4vp_login_form_button'); -// Add an action to call our script enqueuing function -add_action( 'wp_enqueue_script', 'universal_openid4vp_enqueue_personal_wallet_scripts' ); +// Manually fired from the block render callback after a presentation request is prepared +add_action( 'universal_openid4vp_enqueue_personal_wallet_scripts_action', 'universal_openid4vp_enqueue_personal_wallet_scripts' ); add_action( 'wp_ajax_nopriv_universal_openid4vp_poll_status_ajax', 'universal_openid4vp_ajax_poll_status' ); add_action( 'wp_ajax_universal_openid4vp_poll_status_ajax', 'universal_openid4vp_ajax_poll_status' ); @@ -114,15 +122,19 @@ function universal_openid4vp_enqueue_org_wallet_scripts() { * back to the client script as JSON. */ function universal_openid4vp_ajax_poll_status() { - // Get the 'current' data that the AJAX call sent - if ( isset( $_POST['current'] ) ) { - $current = $_POST['current']; - } + check_ajax_referer( 'universal_openid4vp_ajax', 'nonce' ); + + $current = isset( $_POST['current'] ) + ? esc_url_raw( wp_unslash( $_POST['current'] ) ) + : ''; $options = new Universal_OpenID4VP_Admin_Options(); - $response = wp_remote_get( $_SESSION['presentationStatusUri'], array( - 'headers' => array('Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $_SESSION['accessToken'] ), + $presentationStatusUri = universal_openid4vp_session_get( 'presentationStatusUri' ); + $accessToken = universal_openid4vp_session_get( 'accessToken' ); + + $response = wp_remote_get( $presentationStatusUri, array( + 'headers' => array('Content-Type' => 'application/json', 'Authorization' => 'Bearer ' . $accessToken ), 'timeout' => 45, 'redirection' => 5, 'blocking' => true @@ -134,22 +146,22 @@ function universal_openid4vp_ajax_poll_status() { $presentationResponse = json_decode( $body, true); if ( $presentationResponse['status'] === 'authorization_response_verified' ) { - $successUrl = $_SESSION['successUrl']; - - error_log($body); + $successUrl = universal_openid4vp_session_get( 'successUrl' ); $credentialClaims = $presentationResponse['verified_data']['credential_claims']; + $storedPresentationResponse = universal_openid4vp_session_get( 'presentationResponse', array() ); + if ( ! is_array( $storedPresentationResponse ) ) { + $storedPresentationResponse = array(); + } foreach ($credentialClaims as $credential) { - if (empty($_SESSION['presentationResponse'])) { - $_SESSION['presentationResponse'] = []; - } - $_SESSION['presentationResponse'][$credential['id']] = $credential; + $storedPresentationResponse[ $credential['id'] ] = $credential; } + universal_openid4vp_session_set( 'presentationResponse', $storedPresentationResponse ); if ($options->loginUrl == $current) { $jsonAttributeNames = explode(".", $options->usernameAttribute); - $result = $_SESSION['presentationResponse']; + $result = $storedPresentationResponse; foreach ($jsonAttributeNames as &$name) { $result = $result[$name]; } @@ -173,19 +185,19 @@ function universal_openid4vp_ajax_poll_status() { } } - $_SESSION['accessToken'] = null; - $_SESSION['successUrl'] = null; + universal_openid4vp_session_delete( 'accessToken' ); + universal_openid4vp_session_delete( 'successUrl' ); } // Prepare the data to sent back to Javascript $data = array( - 'presentationStatusUri' => $_SESSION['presentationStatusUri'], - 'configuredSuccessUrl' => $_SESSION['successUrl'], + 'presentationStatusUri' => $presentationStatusUri, + 'configuredSuccessUrl' => universal_openid4vp_session_get( 'successUrl' ), 'successUrl' => $successUrl ); // Encode it as JSON and send it back - echo json_encode( $data ); + echo wp_json_encode( $data ); die(); } @@ -194,16 +206,18 @@ function universal_openid4vp_ajax_poll_status() { * back to the client script as JSON. */ function universal_openid4vp_ajax_org_wallet_presentation_exchange() { - $attributes = $_SESSION['queryAttributes']; + check_ajax_referer( 'universal_openid4vp_ajax', 'nonce' ); + + $attributes = universal_openid4vp_session_get( 'queryAttributes', array() ); $response = universal_openid4vp_sendVpRequest($attributes); if ($response["success"] === false) { - echo $response["error"]; + echo wp_kses_post( $response["error"] ); return; } - echo json_encode($response["result"]); + echo wp_json_encode( $response["result"] ); die(); } @@ -241,9 +255,14 @@ function universal_openid4vp_sendVpRequest($attributes) { } $authenticationResult = json_decode( wp_remote_retrieve_body($response) ); - $body = array('query_id' => $attributes['queryId']); + $body = array( 'query_id' => $attributes['queryId'] ); + // Nonce is verified by check_ajax_referer() in the calling AJAX handler + // (universal_openid4vp_ajax_org_wallet_presentation_exchange). On the + // personal-wallet render path this function runs under GET, so $_POST is empty. + // phpcs:ignore WordPress.Security.NonceVerification.Missing if ( isset( $_POST['walletUrl'] ) ) { - $body['request_uri_base'] = $_POST['walletUrl']; + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $body['request_uri_base'] = esc_url_raw( wp_unslash( $_POST['walletUrl'] ) ); } if (array_key_exists('requestUriMethod', $attributes)) { $body['request_uri_method'] = $attributes['requestUriMethod']; @@ -282,7 +301,7 @@ function universal_openid4vp_sendVpRequest($attributes) { 'timeout' => 45, 'redirection' => 5, 'blocking' => true, - 'body' => json_encode($body) + 'body' => wp_json_encode($body) )); if (is_wp_error($response)) { @@ -294,22 +313,22 @@ function universal_openid4vp_sendVpRequest($attributes) { $result = json_decode( $body ); if ( json_last_error() !== JSON_ERROR_NONE ) { - $block_content = '

JSON decode fout: ' . json_last_error_msg().'

'; + $block_content = '

JSON decode fout: ' . esc_html( json_last_error_msg() ) . '

'; return ["success" => false, "error" => $block_content]; } // Controleer op fout in de API response zelf (bijv. foutcode of foutbericht) if ( isset( $result->status ) && isset( $result->detail ) ) { - $block_content = '

API fout: ' . $result->detail.'

'; + $block_content = '

API fout: ' . esc_html( $result->detail ) . '

'; return ["success" => false, "error" => $block_content]; } - // store the correlation id in the SESSION - $_SESSION['correlationId'] = $result->correlation_id; - $_SESSION['presentationStatusUri'] = $result->status_uri; - $_SESSION['accessToken'] = $authenticationResult->access_token; + // store the correlation id in the session store + universal_openid4vp_session_set( 'correlationId', $result->correlation_id ); + universal_openid4vp_session_set( 'presentationStatusUri', $result->status_uri ); + universal_openid4vp_session_set( 'accessToken', $authenticationResult->access_token ); if (array_key_exists('successUrl', $attributes)) { - $_SESSION['successUrl'] = wp_sanitize_redirect($attributes['successUrl']); + universal_openid4vp_session_set( 'successUrl', wp_sanitize_redirect( $attributes['successUrl'] ) ); } return ["success" => true, "result" => $result];