diff --git a/openid4vci-plugin.php b/openid4vci-plugin.php index 73a2502..70baea2 100644 --- a/openid4vci-plugin.php +++ b/openid4vci-plugin.php @@ -2,7 +2,7 @@ /** * Plugin Name: Universal OID4VCI * Description: Issue verifiable credentials using the universal OID4VCI interface with an organization wallet. - * Version: 0.4.0 + * Version: 0.4.9 * Requires at least: 6.6 * Requires PHP: 7.2 * Author: Credenco B.V. @@ -44,6 +44,7 @@ function create_block_openid4vci_block_init() { register_block_type( __DIR__ . '/build/credentialIssue' ); register_block_type( __DIR__ . '/build/credentialIssueOrgWallet' ); + register_block_type( __DIR__ . '/build/credentialIssueRaw' ); if(!session_id()) { session_start(); } @@ -53,6 +54,57 @@ function create_block_openid4vci_block_init() { // Add an action to call our script enqueuing function //add_action( 'wp_enqueue_script', 'enqueue_my_scripts' ); +/** + * Probeert de status_uri van een issuance te pollen voor een voltooide credential. + * + * @param string $statusUri + * @param string $authenticationHeaderName + * @param string $authenticationToken + * @param int $maxAttempts + * @param int $delaySeconds + * + * @return object|null + */ +function openid4vci_fetch_status_result( $statusUri, $authenticationHeaderName, $authenticationToken, $maxAttempts = 3, $delaySeconds = 1 ) { + if ( empty( $statusUri ) ) { + return null; + } + + for ( $attempt = 1; $attempt <= $maxAttempts; $attempt ++ ) { + $statusResponse = wp_remote_get( $statusUri, array( + 'headers' => array( + 'Accept' => 'application/json', + $authenticationHeaderName => $authenticationToken, + ), + 'timeout' => 20, + 'redirection' => 5, + 'blocking' => true, + ) ); + + if ( is_wp_error( $statusResponse ) ) { + error_log( '[openid4vci] status poll fout poging ' . $attempt . ': ' . $statusResponse->get_error_message() ); + } else { + $statusBody = wp_remote_retrieve_body( $statusResponse ); + $statusResult = json_decode( $statusBody ); + + if ( json_last_error() === JSON_ERROR_NONE ) { + if ( isset( $statusResult->credential ) ) { + return $statusResult; + } + error_log( '[openid4vci] status poll poging ' . $attempt . ' zonder credential: ' . $statusBody ); + } else { + error_log( '[openid4vci] status poll JSON fout poging ' . $attempt . ': ' . json_last_error_msg() ); + } + } + + if ( $attempt < $maxAttempts ) { + sleep( $delaySeconds ); + } + } + + return null; +} + function sendVciRequest($claims, $attributes) { $options = new OpenID4VCI_Admin_Options(); $openidEndpoint = $options->openidEndpoint; @@ -119,7 +171,74 @@ function sendVciRequest($claims, $attributes) { return ["success" => false, "error" => $block_content]; } + if ( ! isset( $result->credential ) && isset( $result->status_uri ) ) { + $statusResult = openid4vci_fetch_status_result( + $result->status_uri, + $authenticationHeaderName, + $authenticationToken + ); + + if ( $statusResult ) { + $result = $statusResult; + } + } + return ["success" => true, "result" => $result]; } +function sendRawIssueRequest( $claims, $attributes ) { + $options = new OpenID4VCI_Admin_Options(); + $openidEndpoint = $options->openidEndpoint; + $authenticationHeaderName = $options->authenticationHeaderName; + $authenticationToken = $options->authenticationToken; + + if ( ! empty( $attributes['openidEndpoint'] ) ) { + $openidEndpoint = $attributes['openidEndpoint']; + $authenticationHeaderName = $attributes['authenticationHeaderName']; + $authenticationToken = $attributes['authenticationToken']; + } + + $templateId = $attributes['credentialIssueTemplateKey'] ?? ''; + if ( empty( $templateId ) ) { + $block_content = '

Template ID ontbreekt voor raw issue.

'; + return [ 'success' => false, 'error' => $block_content ]; + } + + $payload = array( + 'claims' => $claims, + 'template_id' => $templateId, + ); + + $payload = wp_json_encode( $payload, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ); + + $response = wp_remote_post( $openidEndpoint, array( + 'headers' => array( + 'Content-Type' => 'application/json', + $authenticationHeaderName => $authenticationToken, + ), + 'timeout' => 45, + 'redirection' => 5, + 'blocking' => true, + 'body' => $payload, + ) ); + + if ( is_wp_error( $response ) ) { + $block_content = '

Raw issue request mislukt: ' . esc_html( $response->get_error_message() ) . '

'; + return [ 'success' => false, 'error' => $block_content ]; + } + $body = wp_remote_retrieve_body( $response ); + $result = json_decode( $body ); + + if ( json_last_error() !== JSON_ERROR_NONE ) { + $block_content = '

Raw issue JSON fout: ' . json_last_error_msg() . '

'; + return [ 'success' => false, 'error' => $block_content ]; + } + + if ( isset( $result->status ) && isset( $result->detail ) ) { + $block_content = '

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

'; + return [ 'success' => false, 'error' => $block_content ]; + } + + return [ 'success' => true, 'result' => $result ]; +} diff --git a/readme.txt b/readme.txt index 951437a..1658642 100644 --- a/readme.txt +++ b/readme.txt @@ -2,7 +2,7 @@ Contributors: The WordPress Contributors Tags: block Tested up to: 6.6 -Stable tag: 0.2.0 +Stable tag: 0.4.9 License: GPL-2.0-or-later License URI: https://www.gnu.org/licenses/gpl-2.0.html diff --git a/src/credentialIssueRaw/block.json b/src/credentialIssueRaw/block.json new file mode 100644 index 0000000..377f5a2 --- /dev/null +++ b/src/credentialIssueRaw/block.json @@ -0,0 +1,56 @@ +{ + "$schema": "https://schemas.wp.org/trunk/block.json", + "apiVersion": 3, + "name": "openid4vci-plugin/openid4vc-issue-raw", + "version": "0.1.0", + "title": "OID4VCI – Raw credential issue", + "category": "widgets", + "description": "Generate a raw SD-JWT credential directly", + "example": {}, + "attributes": { + "openidEndpoint": { + "type": "string" + }, + "authenticationHeaderName": { + "type": "string" + }, + "authenticationToken": { + "type": "string" + }, + "credentialIssueTemplateKey": { + "type": "string" + }, + "credentialData": { + "type": "string" + }, + "formData": { + "type": "string" + }, + "sessionData": { + "type": "string" + }, + "showCredential": { + "type": "boolean", + "default": true + }, + "buttonLabel": { + "type": "string", + "default": "Generate credential" + } + }, + "supports": { + "color": { + "background": false, + "text": true + }, + "html": false, + "typography": { + "fontSize": true + } + }, + "textdomain": "openid4vc-issue", + "editorScript": "file:./index.js", + "editorStyle": "file:./index.css", + "style": "file:./style-index.css", + "render": "file:./render.php" +} diff --git a/src/credentialIssueRaw/index.asset.php b/src/credentialIssueRaw/index.asset.php new file mode 100644 index 0000000..996bf75 --- /dev/null +++ b/src/credentialIssueRaw/index.asset.php @@ -0,0 +1,2 @@ + array('react', 'react-jsx-runtime', 'wp-block-editor', 'wp-blocks', 'wp-components', 'wp-i18n'), 'version' => 'c38a64d9058b58876565'); + diff --git a/src/credentialIssueRaw/index.js b/src/credentialIssueRaw/index.js new file mode 100644 index 0000000..cc5de9d --- /dev/null +++ b/src/credentialIssueRaw/index.js @@ -0,0 +1,2 @@ +(()=>{"use strict";const e=window.wp.blocks,n=window.wp.i18n,t=window.wp.blockEditor,a=window.wp.components,o=(window.React,window.ReactJSXRuntime),i=JSON.parse('{"UU":"openid4vci-plugin/openid4vc-issue-raw"}'),s=(0,o.jsx)("svg",{fill:"#000000",width:"800px",height:"800px",viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,o.jsx)("path",{d:"M4,4h6v6H4V4M20,4v6H14V4h6M14,15h2V13H14V11h2v2h2V11h2v2H18v2h2v3H18v2H16V18H13v2H11V16h3V15m2,0v3h2V15H16M4,20V14h6v6H4M6,6V8H8V6H6M16,6V8h2V6H16M6,16v2H8V16H6M4,11H6v2H4V11m5,0h4v4H11V13H9V11m2-5h2v4H11V6M2,2V6H0V2A2,2,0,0,1,2,0H6V2H2M22,0a2,2,0,0,1,2,2V6H22V2H18V0h4M2,18v4H6v2H2a2,2,0,0,1-2-2V18H2m20,4V18h2v4a2,2,0,0,1-2,2H18V22Z"})});(0,e.registerBlockType)(i.UU,{icon:s,edit:function({attributes:e,setAttributes:i}){const{openidEndpoint:s,credentialIssueTemplateKey:l,authenticationHeaderName:r,authenticationToken:d,credentialData:c,formData:v,sessionData:h,showCredential:u=!0,buttonLabel:p="Genereer credential"}=e;return(0,o.jsxs)(o.Fragment,{children:[(0,o.jsx)(t.InspectorControls,{children:(0,o.jsxs)(a.PanelBody,{title:(0,n.__)("Settings","openid4vc-issue"),children:[(0,o.jsx)(a.TextControl,{label:(0,n.__)("Credential issue template key","openid4vc-issue"),value:l,onChange:e=>i({credentialIssueTemplateKey:e})}),(0,o.jsx)(a.TextareaControl,{label:(0,n.__)("Credential data","openid4vc-issue"),value:c,onChange:e=>i({credentialData:e})}),(0,o.jsx)(a.TextareaControl,{label:(0,n.__)("Form data","openid4vc-issue"),value:v,onChange:e=>i({formData:e})}),(0,o.jsx)(a.TextareaControl,{label:(0,n.__)("Session data","openid4vc-issue"),value:h,onChange:e=>i({sessionData:e})}),(0,o.jsx)(a.ToggleControl,{label:(0,n.__)("Toon credential in blok","openid4vc-issue"),checked:u,onChange:e=>i({showCredential:e})}),(0,o.jsx)(a.TextControl,{label:(0,n.__)("Knoptekst","openid4vc-issue"),value:p,onChange:e=>i({buttonLabel:e})})]})}),(0,o.jsxs)(t.InspectorAdvancedControls,{children:[(0,o.jsx)(a.TextControl,{label:(0,n.__)("Raw issue endpoint","openid4vc-issue"),value:s,onChange:e=>i({openidEndpoint:e})}),(0,o.jsx)(a.TextControl,{label:"Authentication header",value:r,onChange:e=>i({authenticationHeaderName:e})}),(0,o.jsx)(a.TextControl,{label:"Authentication token",value:d,onChange:e=>i({authenticationToken:e})})]}),(0,o.jsx)("p",{...(0,t.useBlockProps)(),children:(0,o.jsx)("strong",{children:(0,n.__)("Raw issue block – configure template ID en endpoint in de instellingen.","openid4vc-issue")})})]})},save:function(){return null}})})(); + diff --git a/src/credentialIssueRaw/render.php b/src/credentialIssueRaw/render.php new file mode 100644 index 0000000..7e424b1 --- /dev/null +++ b/src/credentialIssueRaw/render.php @@ -0,0 +1,200 @@ +key, $item->mapping, $item->type ) ) { + continue; + } + $type = $item->type; + $mapping = $item->mapping; + + $val = null; + if ( isset( $presentationResponse[ $type ]['claims'] ) && is_array( $presentationResponse[ $type ]['claims'] ) ) { + $val = getByPath( $presentationResponse[ $type ]['claims'], (string) $mapping ); + } + + if ( null !== $val ) { + setByPath( $claims, (string) $item->key, $val ); + } + } + } else { + echo '

Er kunnen geen credentials opgehaald worden.

'; + return; + } + } else { + echo '

SessionData is niet geldig.

'; + return; + } +} + +do_action( 'wp_enqueue_script' ); + +$html = ''; +$form = false; +$show_credential = array_key_exists( 'showCredential', $attributes ) ? (bool) $attributes['showCredential'] : true; + +if ( isset( $attributes['formData'] ) && ! empty( $attributes['formData'] ) ) { + $formData = json_decode( $attributes['formData'] ); + if ( json_last_error() === JSON_ERROR_NONE && ( is_object( $formData ) || is_array( $formData ) ) ) { + $form = true; + $html .= '
'; + foreach ( $formData as $key => $value ) { + $html .= '
'; + $html .= ''; + $html .= ''; + $html .= '
'; + } + $html .= ''; + $html .= ''; + $html .= '
'; + } +} + + +$issue_and_render = function () use ( &$claims, $attributes, $show_credential ) { + $response = sendRawIssueRequest( $claims, $attributes ); + + if ( $response['success'] === false ) { + echo $response['error']; + return true; + } + + $result = $response['result']; + if ( isset( $result->credential ) ) { + if ( is_string( $result->credential ) ) { + $payload = $result->credential; + } elseif ( is_object( $result->credential ) && isset( $result->credential->token ) && is_string( $result->credential->token ) ) { + $payload = $result->credential->token; + } else { + $payload = wp_json_encode( $result->credential, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ); + } + } elseif ( isset( $result->token ) && is_string( $result->token ) ) { + $payload = $result->token; + } else { + $payload = wp_json_encode( $result, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT ); + } + + error_log( '[openid4vci] rawIssue credential response: ' . $payload ); + $_SESSION['RawCredential'] = $payload; + + $block_content = '
'; + if ( $show_credential ) { + $block_content .= '

' . __( 'SD-JWT credential:', 'openid4vc-issue' ) . '

' + . ''; + } + $block_content .= '
'; + + echo $block_content; + + return true; +}; + +if ( isset( $_GET['rawissuerequest'] ) ) { + $originalFormKeys = []; + if ( isset( $attributes['formData'] ) && ! empty( $attributes['formData'] ) ) { + $decodedForm = json_decode( $attributes['formData'] ); + if ( $decodedForm && ( is_object( $decodedForm ) || is_array( $decodedForm ) ) ) { + foreach ( $decodedForm as $k => $label ) { + $originalFormKeys[] = (string) $k; + } + } + } + + foreach ( $_GET as $name => $value ) { + if ( 'rawissuerequest' === $name ) { + continue; + } + + $originalKey = $name; + foreach ( $originalFormKeys as $formKey ) { + if ( $formKey === $name ) { + $originalKey = $formKey; + break; + } + if ( str_replace( array( '.', '/' ), '_', $formKey ) === $name ) { + $originalKey = $formKey; + break; + } + } + + setByPath( $claims, $originalKey, sanitize_text_field( wp_unslash( $value ) ) ); + } + + $issue_and_render(); +} elseif ( $form ) { + echo '
' . $html . '
'; +} else { + $submit_label = esc_html( $attributes['buttonLabel'] ?? __( 'Genereer credential', 'openid4vc-issue' ) ); + echo '
' + . '
' + . '' + . '' + . '
' + . '
'; +}