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 ( <>' . 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 ( <>+
JSON decode fout: ' . json_last_error_msg().'
JSON decode fout: ' . esc_html( json_last_error_msg() ) . '
API fout: ' . $result->detail.'
API fout: ' . esc_html( $result->detail ) . '