Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions didit-verify.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Plugin Name: Didit Verify
* Plugin URI: https://github.com/didit-protocol/plugin-wordpress
* Description: Identity verification for WordPress & WooCommerce using the Didit SDK.
* Version: 0.3.3
* Version: 0.3.4
* Author: Didit
* Author URI: https://didit.me
* License: GPL-2.0-or-later
Expand All @@ -18,7 +18,7 @@
exit;
}

define('DIDIT_VERIFY_VERSION', '0.3.3');
define('DIDIT_VERIFY_VERSION', '0.3.4');
define('DIDIT_VERIFY_URL', plugin_dir_url(__FILE__));
define('DIDIT_API_URL', 'https://verification.didit.me/v3/session/');

Expand Down Expand Up @@ -1165,7 +1165,11 @@ private function apply_confirmed_status($session_id, array $record, string $stat
$record['event_timestamp'] = $timestamp;
$this->store_session($session_id, $record);
$user_id = (int) $record['user_id'];
if ($user_id && (string) get_user_meta($user_id, '_didit_pending_session_id', true) === $session_id) {
// A new attempt must not prevent revocation of the session still granting access.
if ($user_id && (
(string) get_user_meta($user_id, '_didit_pending_session_id', true) === $session_id
|| (string) get_user_meta($user_id, '_didit_confirmed_session_id', true) === $session_id
)) {
update_user_meta($user_id, '_didit_session_id', $session_id);
update_user_meta($user_id, '_didit_confirmed_session_id', $session_id);
update_user_meta($user_id, '_didit_status', $status);
Expand Down Expand Up @@ -1194,7 +1198,12 @@ private function confirmed_user_status($user_id): string
if (!$session_id || $session_id !== (string) get_user_meta($user_id, '_didit_session_id', true)) {
return '';
}
return (string) get_user_meta($user_id, '_didit_status', true);
// Read the recorded decision so a revocation received before an upgrade also applies.
$record = $this->stored_session($session_id);
if (!is_array($record) || (int) ($record['user_id'] ?? 0) !== (int) $user_id) {
return '';
}
return $this->normalize_status($record['status'] ?? '');
}

public function is_user_verified($user_id): bool
Expand Down
9 changes: 8 additions & 1 deletion readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Tags: identity verification, kyc, woocommerce, age verification, id check
Requires at least: 6.0
Tested up to: 7.1
Requires PHP: 7.4
Stable tag: 0.3.3
Stable tag: 0.3.4
License: GPL-2.0-or-later
License URI: https://www.gnu.org/licenses/gpl-2.0.html

Expand Down Expand Up @@ -210,6 +210,10 @@ Yes. Go to **Settings → Didit Verify → Display Options → Display Mode**. C

== Changelog ==

= 0.3.4 =
* Security: revoke protected-content access when the approved verification is declined, expires, or returns to review, even if another verification has started.
* Keep a newer approved verification unaffected by updates to an older session.

= 0.3.3 =
* Add Didit branding and grouped cards to the WordPress settings page.
* Show readable verification status badges in Users and WooCommerce order details.
Expand Down Expand Up @@ -290,6 +294,9 @@ Yes. Go to **Settings → Didit Verify → Display Options → Display Mode**. C

== Upgrade Notice ==

= 0.3.4 =
Security update for protected content: revocations now apply to the verification granting access even when another attempt is pending.

= 0.3.3 =
Branded administration and clearer verification results. Includes the server-confirmed approval protections introduced in 0.3.2.

Expand Down
45 changes: 45 additions & 0 deletions tests/test-verification-authorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,4 +61,49 @@ function claim($id, $status = 'Approved') {
internal('store_session', $id, $record);
internal('apply_confirmed_status', $id, $record, 'Approved', time() - 60);
check('Not Started' === internal('stored_session', $id)['status'], 'older signed delivery cannot overwrite newer state');

// A second attempt must not prevent revocation of the session granting access.
$pending_id = 'bbbbbbbb-bbbb-cccc-dddd-eeeeeeeeeeee';
$secret = 'test-only-webhook-secret';
$GLOBALS['didit_test_options']['didit_webhook_secret'] = $secret;
function signed_status($id, $status, $timestamp) {
$body = json_encode(['session_id' => $id, 'status' => $status,
'timestamp' => $timestamp, 'webhook_type' => 'status.updated']);
return Didit_Verify::init()->rest_webhook(new Didit_Test_Request($body, [
'X-Signature' => hash_hmac('sha256', $body, $GLOBALS['didit_test_options']['didit_webhook_secret']),
]));
}
foreach (['Declined', 'Expired', 'KYC Expired', 'In Review'] as $status) {
$record['event_timestamp'] = time() - 2;
$record['status'] = 'Approved';
internal('store_session', $id, $record);
$GLOBALS['didit_test_user_meta']['42|_didit_session_id'] = $id;
$GLOBALS['didit_test_user_meta']['42|_didit_confirmed_session_id'] = $id;
$GLOBALS['didit_test_user_meta']['42|_didit_status'] = 'Approved';
$GLOBALS['didit_test_user_meta']['42|_didit_verified'] = 1;
$GLOBALS['didit_test_user_meta']['42|_didit_pending_session_id'] = $pending_id;
signed_status($id, $status, time());
check(!Didit_Verify::init()->is_user_verified(42), "$status revokes the current approval while another session is pending");
check($status === internal('confirmed_user_status', 42), "$status is displayed on the user after revocation");
check('' === get_user_meta(42, '_didit_verified', true), "$status clears the verified flag");
}
$record['event_timestamp'] = 0;
$record['status'] = 'Not Started';
internal('store_session', $pending_id, $record);
signed_status($pending_id, 'Approved', time());
check(Didit_Verify::init()->is_user_verified(42), 'the new pending session can approve the user');
signed_status($id, 'Declined', time() + 1);
check(Didit_Verify::init()->is_user_verified(42), 'a superseded session cannot revoke the new approval');
check($pending_id === get_user_meta(42, '_didit_confirmed_session_id', true), 'a superseded session cannot replace the active session');
signed_status($pending_id, 'Declined', time() - 1);
check(Didit_Verify::init()->is_user_verified(42), 'an older revocation cannot overwrite a newer approval');
// Version 0.3.3 could already have recorded a revocation without updating user meta.
$revoked_record = internal('stored_session', $pending_id);
$revoked_record['status'] = 'Declined';
internal('store_session', $pending_id, $revoked_record);
check(!Didit_Verify::init()->is_user_verified(42), 'an existing recorded revocation overrides stale approval metadata after upgrade');
$revoked_record['status'] = 'Approved';
$revoked_record['user_id'] = 7;
internal('store_session', $pending_id, $revoked_record);
check(!Didit_Verify::init()->is_user_verified(42), 'another user\'s session cannot supply the current approval');
echo "$passed passed\n";
Loading