From 5cd2237fadf5155a837c2f4bd18c66e76ac6d8f0 Mon Sep 17 00:00:00 2001 From: Lai Power Date: Wed, 3 Jun 2026 21:27:37 +0000 Subject: [PATCH] updated plugin `WP-WebAuthn` version 1.4.1 --- wp-content/plugins/wp-webauthn/changelog.txt | 45 ++ wp-content/plugins/wp-webauthn/css/admin.css | 6 + wp-content/plugins/wp-webauthn/css/login.css | 25 + wp-content/plugins/wp-webauthn/js/admin.js | 6 +- .../plugins/wp-webauthn/js/default_wa.js | 31 +- wp-content/plugins/wp-webauthn/js/frontend.js | 35 +- wp-content/plugins/wp-webauthn/js/login.js | 264 +++++-- wp-content/plugins/wp-webauthn/js/profile.js | 83 +- .../wp-webauthn/languages/template.pot | 707 +++++++++++------- wp-content/plugins/wp-webauthn/readme.txt | 89 +-- .../wp-webauthn-vendor/autoload.php | 5 +- .../beberlei/assert/.github/workflows/ci.yml | 64 -- .../beberlei/assert/composer.json | 2 +- .../beberlei/assert/lib/Assert/Assert.php | 6 +- .../beberlei/assert/lib/Assert/Assertion.php | 180 ++--- .../assert/lib/Assert/AssertionChain.php | 2 +- .../lib/Assert/InvalidArgumentException.php | 2 +- .../assert/lib/Assert/LazyAssertion.php | 2 +- .../beberlei/assert/lib/Assert/functions.php | 6 +- .../composer/InstalledVersions.php | 45 +- .../composer/autoload_psr4.php | 6 +- .../composer/autoload_static.php | 84 +-- .../composer/installed.json | 88 +-- .../wp-webauthn-vendor/composer/installed.php | 40 +- .../symfony/polyfill-php80/PhpToken.php | 7 +- .../symfony/polyfill-php81/composer.json | 2 +- .../symfony/process/ExecutableFinder.php | 59 +- .../symfony/process/PhpExecutableFinder.php | 11 +- .../symfony/process/Process.php | 16 +- .../plugins/wp-webauthn/wp-webauthn.php | 284 ++++++- .../plugins/wp-webauthn/wwa-admin-content.php | 303 ++++++-- wp-content/plugins/wp-webauthn/wwa-ajax.php | 628 ++++++++++------ .../plugins/wp-webauthn/wwa-compatibility.php | 60 +- .../plugins/wp-webauthn/wwa-functions.php | 300 +++++--- wp-content/plugins/wp-webauthn/wwa-menus.php | 21 +- .../wp-webauthn/wwa-network-admin-content.php | 198 +++++ .../wp-webauthn/wwa-profile-content.php | 138 ++-- .../plugins/wp-webauthn/wwa-shortcodes.php | 67 +- .../plugins/wp-webauthn/wwa-version.php | 8 +- 39 files changed, 2660 insertions(+), 1265 deletions(-) create mode 100644 wp-content/plugins/wp-webauthn/changelog.txt delete mode 100644 wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/.github/workflows/ci.yml create mode 100644 wp-content/plugins/wp-webauthn/wwa-network-admin-content.php diff --git a/wp-content/plugins/wp-webauthn/changelog.txt b/wp-content/plugins/wp-webauthn/changelog.txt new file mode 100644 index 00000000..bbe28728 --- /dev/null +++ b/wp-content/plugins/wp-webauthn/changelog.txt @@ -0,0 +1,45 @@ += 1.2.8 = +Fix: privilege check for admin panel + += 1.2.7 = +Add: Now a security warning will be shown if user verification is disabled +Fix: Style broken with some locales +Fix: privilege check for admin panel (thanks to @vanpop) +Update: Third party libraries + += 1.2.6 = +Update: Third party libraries + += 1.2.5 = +Update: German translation (thanks to niiconn) +Fix: HTTPS check + += 1.2.4 = +Add: French translation (thanks to Spomky) and Turkish translate (thanks to Sn0bzy) +Fix: HTTPS check +Update: Existing translations +Update: Third party libraries + += 1.2.3 = +Feat: Avoid locking users out if WebAuthn is not available +Update: translations +Update: Third party libraries + += 1.2.2 = +Fix: Cannot access to js files in apache 2.4+ + += 1.2.1 = +Feat: Allow to disable password login completely +Feat: Now we use WordPress transients instead of PHP sessions +Feat: Move register related settings to user's profile +Feat: Gutenberg block support +Feat: Traditional Chinese (Hong Kong) & Traditional Chinese (Taiwan) translation +Update: Chinese translation +Update: Third-party libraries + += 1.1.0 = +Add: Allow to remember login option +Add: Only allow a specific type of authenticator option +Fix: Toggle button may not working in login form +Update: Chinese translation +Update: Third-party libraries \ No newline at end of file diff --git a/wp-content/plugins/wp-webauthn/css/admin.css b/wp-content/plugins/wp-webauthn/css/admin.css index 73f3bc3b..e1bf5be3 100644 --- a/wp-content/plugins/wp-webauthn/css/admin.css +++ b/wp-content/plugins/wp-webauthn/css/admin.css @@ -51,4 +51,10 @@ } #wp-webauthn-uv-warning { margin: 15px 0 0; +} +.wwa-table-svg { + display: inline-block; + margin-right: 5px; + vertical-align: middle; + width: 20px; } \ No newline at end of file diff --git a/wp-content/plugins/wp-webauthn/css/login.css b/wp-content/plugins/wp-webauthn/css/login.css index bb1f8895..8feeb12b 100644 --- a/wp-content/plugins/wp-webauthn/css/login.css +++ b/wp-content/plugins/wp-webauthn/css/login.css @@ -1,5 +1,6 @@ #wp-webauthn { margin-right: 5px; + width: 56px; } #wp-webauthn span { line-height: 30px; @@ -47,8 +48,32 @@ #loginform.wwa-webauthn-only { padding-bottom: 30px; } +#loginform .wwa-passkey-notice { + display: flex; + align-items: center; + justify-content: center; + gap: 5px; +} +#loginform .password-icon { + margin-right: 4px; +} +#loginform .password-icon::after { + display: inline-block; + content: ""; + width: 1px; + height: 80%; + background-color: currentColor; + position: relative; + left: 3px; + bottom: 10%; +} @media(max-width: 782px){ #wp-webauthn span { line-height: 38px; } +} +@media(max-width: 782px){ + .interim-login #wp-webauthn span { + line-height: 30px; + } } \ No newline at end of file diff --git a/wp-content/plugins/wp-webauthn/js/admin.js b/wp-content/plugins/wp-webauthn/js/admin.js index b2d0a6cf..b1db993e 100644 --- a/wp-content/plugins/wp-webauthn/js/admin.js +++ b/wp-content/plugins/wp-webauthn/js/admin.js @@ -33,7 +33,8 @@ function updateLog() { url: php_vars.ajax_url, type: 'GET', data: { - action: 'wwa_get_log' + action: 'wwa_get_log', + _ajax_nonce: php_vars._ajax_nonce }, success: function (data) { if (typeof data === 'string') { @@ -71,7 +72,8 @@ jQuery('#clear_log').click((e) => { url: php_vars.ajax_url, type: 'GET', data: { - action: 'wwa_clear_log' + action: 'wwa_clear_log', + _ajax_nonce: php_vars._ajax_nonce }, success: function () { updateLog(); diff --git a/wp-content/plugins/wp-webauthn/js/default_wa.js b/wp-content/plugins/wp-webauthn/js/default_wa.js index ca8632b7..0aac272e 100644 --- a/wp-content/plugins/wp-webauthn/js/default_wa.js +++ b/wp-content/plugins/wp-webauthn/js/default_wa.js @@ -8,44 +8,44 @@ document.addEventListener('DOMContentLoaded', () => { return; } window.onload = () => { - if (php_vars.webauthn_only === 'true') { + if (wwa_login_php_vars.webauthn_only === 'true') { if ((window.PublicKeyCredential === undefined || navigator.credentials.create === undefined || typeof navigator.credentials.create !== 'function')) { // Not support, show a message if (document.querySelectorAll('#login > h1').length > 0) { let dom = document.createElement('p'); dom.className = 'message'; - dom.innerHTML = php_vars.i18n_8; + dom.innerHTML = wwa_login_php_vars.i18n_8; document.querySelectorAll('#login > h1')[0].parentNode.insertBefore(dom, document.querySelectorAll('#login > h1')[0].nextElementSibling) } } wwa_dom('#loginform', (dom) => { dom.classList.add('wwa-webauthn-only') }); if (document.getElementsByClassName('user-pass-wrap').length > 0) { wwa_dom('.user-pass-wrap, #wp-submit', (dom) => { dom.parentNode.removeChild(dom) }); - if (php_vars.remember_me === 'false' ) { + if (wwa_login_php_vars.remember_me === 'false' ) { wwa_dom('.forgetmenot', (dom) => { dom.parentNode.removeChild(dom) }); } } else { // WordPress 5.2- wwa_dom('#wp-submit', (dom) => { dom.parentNode.removeChild(dom) }); - if (php_vars.remember_me === 'false' ) { + if (wwa_login_php_vars.remember_me === 'false' ) { wwa_dom('.forgetmenot', (dom) => { dom.parentNode.removeChild(dom) }); } const targetDOM = document.getElementById('loginform').getElementsByTagName('p')[1]; targetDOM.parentNode.removeChild(targetDOM); } } - if (!(window.PublicKeyCredential === undefined || navigator.credentials.create === undefined || typeof navigator.credentials.create !== 'function') || php_vars.webauthn_only === 'true') { + if (!(window.PublicKeyCredential === undefined || navigator.credentials.create === undefined || typeof navigator.credentials.create !== 'function') || wwa_login_php_vars.webauthn_only === 'true') { // If supported, toggle - if (php_vars.webauthn_only !== 'true') { + if (wwa_login_php_vars.webauthn_only !== 'true') { if (document.getElementsByClassName('user-pass-wrap').length > 0) { wwa_dom('.user-pass-wrap, #wp-submit', (dom) => { dom.style.display = 'none' }); - if (php_vars.remember_me === 'false' ) { + if (wwa_login_php_vars.remember_me === 'false' ) { wwa_dom('.forgetmenot', (dom) => { dom.style.display = 'none' }); } } else { // WordPress 5.2- wwa_dom('#wp-submit', (dom) => { dom.style.display = 'none' }); - if (php_vars.remember_me === 'false' ) { + if (wwa_login_php_vars.remember_me === 'false' ) { wwa_dom('.forgetmenot', (dom) => { dom.style.display = 'none' }); } document.getElementById('loginform').getElementsByTagName('p')[1].style.display = 'none'; @@ -53,8 +53,17 @@ document.addEventListener('DOMContentLoaded', () => { } wwa_dom('wp-webauthn-notice', (dom) => { dom.style.display = 'flex' }, 'class'); wwa_dom('wp-webauthn-check', (dom) => { dom.style.cssText = `${dom.style.cssText}display: block !important` }, 'id'); - wwa_dom('user_login', (dom) => { dom.focus() }, 'id'); + wwa_dom('user_login', (dom) => { + dom.setAttribute('autocomplete', 'username webauthn'); + setTimeout(() => { + dom.focus(); + }, 0); + }, 'id'); wwa_dom('wp-submit', (dom) => { dom.disabled = true }, 'id'); + // Start Conditional UI (passkey autofill) once WebAuthn mode is active + if (typeof wwa_start_conditional_ui === 'function') { + wwa_start_conditional_ui(); + } } if (document.querySelectorAll('#lostpasswordform, #registerform, .admin-email-confirm-form, #resetpassform').length > 0) { return; @@ -64,9 +73,9 @@ document.addEventListener('DOMContentLoaded', () => { if (dom.length > 0) { if (dom[0].getElementsByTagName('input').length > 0) { // WordPress 5.2- - dom[0].innerHTML = `${php_vars.email_login === 'true' ? php_vars.i18n_10 : php_vars.i18n_9}${dom[0].innerHTML.split('
')[1]}`; + dom[0].innerHTML = `${wwa_login_php_vars.email_login === 'true' ? wwa_login_php_vars.i18n_10 : wwa_login_php_vars.i18n_9}${dom[0].innerHTML.split('
')[1]}`; } else { - dom[0].innerText = php_vars.email_login === 'true' ? php_vars.i18n_10 : php_vars.i18n_9; + dom[0].innerText = wwa_login_php_vars.email_login === 'true' ? wwa_login_php_vars.i18n_10 : wwa_login_php_vars.i18n_9; } } } diff --git a/wp-content/plugins/wp-webauthn/js/frontend.js b/wp-content/plugins/wp-webauthn/js/frontend.js index 33610dff..40e3c72b 100644 --- a/wp-content/plugins/wp-webauthn/js/frontend.js +++ b/wp-content/plugins/wp-webauthn/js/frontend.js @@ -305,7 +305,8 @@ function wwa_bind() { alert(wwa_php_vars.i18n_12); return; } - let wwa_type = this.parentNode.parentNode.getElementsByClassName('wwa-authenticator-type')[0].value; + const wwa_type_el = this.parentNode.parentNode.getElementsByClassName('wwa-authenticator-type')[0]; + let wwa_type = wwa_type_el ? wwa_type_el.value : (wwa_php_vars.allow_authenticator_type !== 'none' ? wwa_php_vars.allow_authenticator_type : 'none'); let wwa_usernameless = this.parentNode.parentNode.querySelectorAll('.wwa-authenticator-usernameless:checked')[0] ? this.parentNode.parentNode.querySelectorAll('.wwa-authenticator-usernameless:checked')[0].value : 'false'; button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_3; wwa_disable_buttons(); @@ -314,7 +315,7 @@ function wwa_bind() { wwa_dom('wwa-authenticator-type', (dom) => { dom.disabled = true }, 'class'); wwa_dom('wwa-authenticator-usernameless', (dom) => { dom.disabled = true }, 'class'); let request = wwa_ajax(); - request.get(wwa_php_vars.ajax_url, `?action=wwa_create&name=${encodeURIComponent(wwa_name)}&type=${encodeURIComponent(wwa_type)}&usernameless=${wwa_usernameless}`, (rawData, status) => { + request.get(wwa_php_vars.ajax_url, `?action=wwa_create&name=${encodeURIComponent(wwa_name)}&type=${encodeURIComponent(wwa_type)}&usernameless=${wwa_usernameless}&_ajax_nonce=${wwa_php_vars._ajax_nonce}`, (rawData, status) => { if (status) { button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_28; let data = rawData; @@ -381,7 +382,7 @@ function wwa_bind() { return publicKeyCredential; }).then(JSON.stringify).then((AuthenticatorAttestationResponse) => { let response = wwa_ajax(); - response.post(`${wwa_php_vars.ajax_url}?action=wwa_create_response`, `data=${encodeURIComponent(window.btoa(AuthenticatorAttestationResponse))}&name=${encodeURIComponent(wwa_name)}&type=${encodeURIComponent(wwa_type)}&usernameless=${wwa_usernameless}&clientid=${clientID}`, (rawData, status) => { + response.post(`${wwa_php_vars.ajax_url}?action=wwa_create_response`, `data=${encodeURIComponent(window.btoa(AuthenticatorAttestationResponse))}&name=${encodeURIComponent(wwa_name)}&type=${encodeURIComponent(wwa_type)}&usernameless=${wwa_usernameless}&clientid=${clientID}&_ajax_nonce=${wwa_php_vars._ajax_nonce}`, (rawData, status) => { if (status) { if (rawData === 'true') { button_dom.nextElementSibling.innerHTML = wwa_php_vars.i18n_29; @@ -521,19 +522,33 @@ function wwa_verify() { } // Update authenticator list +// Compute current number of visible columns for colspan +function getFrontendColspan() { + let cols = 4; // Identifier, Registered, Last used, Action + const typeTh = document.getElementsByClassName('wwa-type-th')[0]; + if (typeTh && typeTh.style.display !== 'none') { + cols++; + } + const ulTh = document.getElementsByClassName('wwa-usernameless-th')[0]; + if (ulTh && ulTh.style.display !== 'none') { + cols++; + } + return cols; +} + function updateList() { if (document.getElementsByClassName('wwa-authenticator-list').length === 0) { return; } let request = wwa_ajax(); - request.get(wwa_php_vars.ajax_url, '?action=wwa_authenticator_list', (rawData, status) => { + request.get(wwa_php_vars.ajax_url, `?action=wwa_authenticator_list&_ajax_nonce=${wwa_php_vars._ajax_nonce}`, (rawData, status) => { if (status) { let data = rawData; try { data = JSON.parse(rawData); } catch (e) { console.warn(rawData); - wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `${wwa_php_vars.i18n_17}` }, 'class'); + wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `${wwa_php_vars.i18n_17}` }, 'class'); return; } if (data.length === 0) { @@ -542,7 +557,7 @@ function updateList() { } else { wwa_dom('.wwa-usernameless-th, .wwa-usernameless-td', (dom) => { dom.style.display = 'none' }); } - wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `${wwa_php_vars.i18n_23}` }, 'class'); + wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `${wwa_php_vars.i18n_23}` }, 'class'); wwa_dom('wwa-authenticator-list-usernameless-tip', (dom) => { dom.innerText = '' }, 'class'); wwa_dom('wwa-authenticator-list-type-tip', (dom) => { dom.innerText = '' }, 'class'); return; @@ -561,7 +576,7 @@ function updateList() { item_type_disabled = true; } } - htmlStr += `${item.name}${item.type === 'none' ? wwa_php_vars.i18n_24 : (item.type === 'platform' ? wwa_php_vars.i18n_25 : wwa_php_vars.i18n_26)}${item_type_disabled ? wwa_php_vars.i18n_35 : ''}${item.added}${item.last_used}${item.usernameless ? wwa_php_vars.i18n_1 + (wwa_php_vars.usernameless === 'true' ? '' : wwa_php_vars.i18n_9) : wwa_php_vars.i18n_8}${wwa_php_vars.i18n_20} | ${wwa_php_vars.i18n_27}`; + htmlStr += `${item.name}${wwa_php_vars.show_authenticator_type === 'true' ? `${item.type === 'none' ? wwa_php_vars.i18n_24 : (item.type === 'platform' ? wwa_php_vars.i18n_25 : wwa_php_vars.i18n_26)}${item_type_disabled ? wwa_php_vars.i18n_35 : ''}` : ''}${item.added}${item.last_used}${item.usernameless ? wwa_php_vars.i18n_1 + (wwa_php_vars.usernameless === 'true' ? '' : wwa_php_vars.i18n_9) : wwa_php_vars.i18n_8}${wwa_php_vars.i18n_20} | ${wwa_php_vars.i18n_27}`; } wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = htmlStr }, 'class'); if (has_usernameless || wwa_php_vars.usernameless === 'true') { @@ -586,7 +601,7 @@ function updateList() { wwa_dom('wwa-authenticator-list-type-tip', (dom) => { dom.innerText = ''; dom.style.display = 'none' }, 'class'); } } else { - wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `${wwa_php_vars.i18n_17}` }, 'class'); + wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `${wwa_php_vars.i18n_17}` }, 'class'); } }) } @@ -603,7 +618,7 @@ function renameAuthenticator(id, name) { } else if (new_name !== null && new_name !== name) { let request = wwa_ajax(); wwa_dom(`wwa-key-${id}`, (dom) => { dom.innerText = wwa_php_vars.i18n_22 }, 'class'); - request.get(wwa_php_vars.ajax_url, `?action=wwa_modify_authenticator&id=${encodeURIComponent(id)}&name=${encodeURIComponent(new_name)}&target=rename`, (data, status) => { + request.get(wwa_php_vars.ajax_url, `?action=wwa_modify_authenticator&id=${encodeURIComponent(id)}&name=${encodeURIComponent(new_name)}&target=rename&_ajax_nonce=${wwa_php_vars._ajax_nonce}`, (data, status) => { if (status) { updateList(); } else { @@ -623,7 +638,7 @@ function removeAuthenticator(id, name) { if (confirm(wwa_php_vars.i18n_18 + name + (document.getElementsByClassName('wwa-authenticator-list')[0].children.length === 1 ? '\n' + wwa_php_vars.i18n_34 : ''))) { wwa_dom(`wwa-key-${id}`, (dom) => { dom.innerText = wwa_php_vars.i18n_19 }, 'class'); let request = wwa_ajax(); - request.get(wwa_php_vars.ajax_url, `?action=wwa_modify_authenticator&id=${encodeURIComponent(id)}&target=remove`, (data, status) => { + request.get(wwa_php_vars.ajax_url, `?action=wwa_modify_authenticator&id=${encodeURIComponent(id)}&target=remove&_ajax_nonce=${wwa_php_vars._ajax_nonce}`, (data, status) => { if (status) { updateList(); } else { diff --git a/wp-content/plugins/wp-webauthn/js/login.js b/wp-content/plugins/wp-webauthn/js/login.js index bf69e7b4..355a30ab 100644 --- a/wp-content/plugins/wp-webauthn/js/login.js +++ b/wp-content/plugins/wp-webauthn/js/login.js @@ -1,5 +1,8 @@ 'use strict'; +const wwa_passkey_notice_svg = ''; +const wwa_passkey_btn_svg = ''; + // Send an AJAX request and get the response const wwa_ajax = function () { let xmlHttpReq = new XMLHttpRequest(); @@ -70,19 +73,154 @@ const wwa_dom = (selector, callback = () => { }, method = 'query') => { } let wwaSupported = true; +let wwa_conditional_ui_abort = null; +let wwa_conditional_ui_active = false; + +/** + * Start a Conditional UI (passkey autofill) request. + * The browser will show a passkey picker in the username autocomplete dropdown. + * Aborted automatically when the user manually triggers a normal WebAuthn check. + */ +function wwa_start_conditional_ui() { + if (wwa_conditional_ui_active) return; + if (!window.PublicKeyCredential) return; + + // Prefer getClientCapabilities() (Chrome 128+) for a richer capability check; + // fall back to isConditionalMediationAvailable() for older browsers. + const conditionalAvailablePromise = + typeof PublicKeyCredential.getClientCapabilities === 'function' + ? PublicKeyCredential.getClientCapabilities().then((caps) => !!caps.conditionalGet) + : typeof PublicKeyCredential.isConditionalMediationAvailable === 'function' + ? PublicKeyCredential.isConditionalMediationAvailable() + : Promise.resolve(false); + + conditionalAvailablePromise.then((available) => { + if (!available) return; + + wwa_conditional_ui_active = true; + wwa_conditional_ui_abort = new AbortController(); + const signal = wwa_conditional_ui_abort.signal; + + // Request an empty-user challenge (usernameless style: no allowCredentials) + let startReq = wwa_ajax(); + startReq.get(wwa_login_php_vars.ajax_url, + '?action=wwa_auth_start&type=auth&usernameless=true&conditional=true', + (rawData, status) => { + if (!status || signal.aborted) { + wwa_conditional_ui_active = false; + return; + } + let data; + try { + data = JSON.parse(rawData); + } catch (e) { + wwa_conditional_ui_active = false; + return; + } + + data.challenge = Uint8Array.from( + window.atob(base64url2base64(data.challenge)), + (c) => c.charCodeAt(0) + ); + if (data.allowCredentials) { + data.allowCredentials = data.allowCredentials.map((item) => { + item.id = Uint8Array.from( + window.atob(base64url2base64(item.id)), + (c) => c.charCodeAt(0) + ); + return item; + }); + } + + const clientID = data.clientID; + delete data.clientID; + + navigator.credentials.get({ + publicKey: data, + mediation: 'conditional', + signal: signal + }).then((credentialInfo) => { + if (!credentialInfo) { + wwa_conditional_ui_active = false; + return; + } + // Show authenticating state + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_5; }, 'class'); + wwa_dom('user_login', (dom) => { dom.readOnly = true; }, 'id'); + wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = true; }); + + const publicKeyCredential = { + id: credentialInfo.id, + type: credentialInfo.type, + rawId: arrayToBase64String(new Uint8Array(credentialInfo.rawId)), + response: { + authenticatorData: arrayToBase64String(new Uint8Array(credentialInfo.response.authenticatorData)), + clientDataJSON: arrayToBase64String(new Uint8Array(credentialInfo.response.clientDataJSON)), + signature: arrayToBase64String(new Uint8Array(credentialInfo.response.signature)), + userHandle: credentialInfo.response.userHandle + ? arrayToBase64String(new Uint8Array(credentialInfo.response.userHandle)) + : null + } + }; + + const AuthenticatorResponse = JSON.stringify(publicKeyCredential); + // The server needs the usernameless path, so username is empty + const userField = document.getElementById('user_login'); + const username = userField ? userField.value : ''; + + let response = wwa_ajax(); + response.post( + `${wwa_login_php_vars.ajax_url}?action=wwa_auth`, + `data=${encodeURIComponent(window.btoa(AuthenticatorResponse))}&type=auth&clientid=${clientID}&user=${encodeURIComponent(username)}&conditional=true&remember=${wwa_login_php_vars.remember_me === 'false' ? 'false' : (document.getElementById('rememberme') ? (document.getElementById('rememberme').checked ? 'true' : 'false') : 'false')}` , + (resData, resStatus) => { + wwa_conditional_ui_active = false; + if (resStatus && resData === 'true') { + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_6; }, 'class'); + const redirectInput = document.querySelector('p.submit input[name="redirect_to"]'); + const redirectTo = redirectInput + ? redirectInput.value + : (getQueryString('redirect_to') || wwa_login_php_vars.admin_url); + setTimeout(() => { window.location.href = redirectTo; }, 200); + } else { + wwa_dom('wp-webauthn-notice', (dom) => { + dom.innerHTML = wwa_login_php_vars.terminology === 'passkey' + ? `${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}` + : ` ${wwa_login_php_vars.i18n_2}`; + }, 'class'); + wwa_dom('user_login', (dom) => { dom.readOnly = false; }, 'id'); + wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = false; }); + // Restart Conditional UI after a failed attempt + wwa_start_conditional_ui(); + } + } + ); + }).catch((err) => { + wwa_conditional_ui_active = false; + // AbortError is expected when check() aborts — do nothing + if (err && err.name !== 'AbortError') { + console.warn('WP-WebAuthn Conditional UI error:', err); + } + }); + } + ); + }).catch(() => { + // isConditionalMediationAvailable not supported or threw — silently ignore + }); +} + document.addEventListener('DOMContentLoaded', () => { - if (document.querySelector('p#nav') && php_vars.password_reset !== 'false') { + if (document.querySelector('p#nav') && wwa_login_php_vars.password_reset !== 'false') { const placeholder = document.getElementById('wwa-lost-password-link-placeholder'); if (placeholder) { const previous = placeholder.previousSibling; const next = placeholder.nextElementSibling; - if (previous && previous.nodeType === Node.TEXT_NODE && previous.data.trim() === php_vars.separator.trim()) { + if (previous && previous.nodeType === Node.TEXT_NODE && previous.data.trim() === wwa_login_php_vars.separator.trim()) { previous.remove(); - } else if (next && next.nodeType === Node.TEXT_NODE && next.data.trim() === php_vars.separator.trim()) { + } else if (next && next.nodeType === Node.TEXT_NODE && next.data.trim() === wwa_login_php_vars.separator.trim() && !next.nextElementSibling) { next.remove(); } + placeholder.remove(); } - placeholder.remove(); } if (document.querySelectorAll('#lostpasswordform, #registerform, .admin-email-confirm-form, #resetpassform').length > 0) { return; @@ -95,24 +233,25 @@ document.addEventListener('DOMContentLoaded', () => { button_check.id = 'wp-webauthn-check'; button_check.type = 'button'; button_check.className = 'button button-large button-primary'; - button_check.innerHTML = php_vars.i18n_1; + button_check.innerHTML = wwa_login_php_vars.i18n_1; let button_toggle = document.createElement('button'); - if (php_vars.webauthn_only !== 'true') { + if (wwa_login_php_vars.webauthn_only !== 'true') { button_toggle.id = 'wp-webauthn'; button_toggle.type = 'button'; button_toggle.className = 'button button-large'; - button_toggle.innerHTML = ''; + button_toggle.innerHTML = ''; + button_toggle.title = wwa_login_php_vars.i18n_13; } let submit = document.getElementById('wp-submit'); if (submit) { - if (php_vars.webauthn_only !== 'true') { + if (wwa_login_php_vars.webauthn_only !== 'true') { submit.parentNode.insertBefore(button_toggle, submit.nextElementSibling); } submit.parentNode.insertBefore(button_check, submit.nextElementSibling); } let notice = document.createElement('div'); notice.className = 'wp-webauthn-notice'; - notice.innerHTML = ` ${php_vars.i18n_2}`; + notice.innerHTML = wwa_login_php_vars.terminology ==='passkey' ? `${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}` : ` ${wwa_login_php_vars.i18n_2}`; let forgetmenot = document.getElementsByClassName('forgetmenot'); if (forgetmenot.length > 0) { forgetmenot[0].parentNode.insertBefore(notice, forgetmenot[0]); @@ -211,46 +350,71 @@ function toggle() { wwa_dom('wp-webauthn-notice', (dom) => { dom.style.display = 'none' }, 'class'); wwa_dom('wp-webauthn-check', (dom) => { dom.style.cssText = `${dom.style.cssText.split('display: block !important')[0]}display: none !important` }, 'id'); wwa_dom('user_pass', (dom) => { dom.disabled = false }, 'id'); - wwa_dom('user_login', (dom) => { dom.focus() }, 'id'); - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = ` ${php_vars.i18n_2}` }, 'class'); + wwa_dom('user_login', (dom) => { + dom.setAttribute('autocomplete', 'username'); + setTimeout(() => { + dom.focus(); + }, 0); + }, 'id'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.terminology ==='passkey' ? `${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}` : ` ${wwa_login_php_vars.i18n_2}` }, 'class'); wwa_dom('wp-submit', (dom) => { dom.disabled = false }, 'id'); + wwa_dom('wp-webauthn', wwa_login_php_vars.terminology ==='passkey' ? (dom) => { + dom.innerHTML = wwa_passkey_btn_svg; + dom.title = wwa_login_php_vars.i18n_14; + dom.style.lineHeight = '0'; + } : (dom) => { + dom.innerHTML = ''; + dom.title = wwa_login_php_vars.i18n_14; + }, 'id'); let inputDom = document.querySelectorAll('#loginform label') if (inputDom.length > 0) { if (document.getElementById('wwa-username-label')) { // WordPress 5.2- - document.getElementById('wwa-username-label').innerText = php_vars.i18n_10; + document.getElementById('wwa-username-label').innerText = wwa_login_php_vars.i18n_10; } else { - inputDom[0].innerText = php_vars.i18n_10; + inputDom[0].innerText = wwa_login_php_vars.i18n_10; } } } else { if (document.getElementsByClassName('user-pass-wrap').length > 0) { wwa_dom('.user-pass-wrap, #wp-submit', (dom) => { dom.style.display = 'none' }); - if (php_vars.remember_me === 'false' ) { + if (wwa_login_php_vars.remember_me === 'false' ) { wwa_dom('.forgetmenot', (dom) => { dom.style.display = 'none' }); } } else { // WordPress 5.2- wwa_dom('#wp-submit', (dom) => { dom.style.display = 'none' }); - if (php_vars.remember_me === 'false' ) { + if (wwa_login_php_vars.remember_me === 'false' ) { wwa_dom('.forgetmenot', (dom) => { dom.style.display = 'none' }); } document.getElementById('loginform').getElementsByTagName('p')[1].style.display = 'none'; } wwa_dom('wp-webauthn-notice', (dom) => { dom.style.display = 'flex' }, 'class'); wwa_dom('wp-webauthn-check', (dom) => { dom.style.cssText = `${dom.style.cssText.split('display: none !important')[0]}display: block !important` }, 'id'); - wwa_dom('user_login', (dom) => { dom.focus() }, 'id'); - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = ` ${php_vars.i18n_2}` }, 'class'); + wwa_dom('user_login', (dom) => { + dom.setAttribute('autocomplete', 'username webauthn'); + setTimeout(() => { + dom.focus(); + }, 0); + }, 'id'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.terminology ==='passkey' ? `${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}` : ` ${wwa_login_php_vars.i18n_2}` }, 'class'); wwa_dom('wp-submit', (dom) => { dom.disabled = true }, 'id'); + wwa_dom('wp-webauthn', (dom) => { + dom.innerHTML = ''; + dom.title = wwa_login_php_vars.i18n_13; + dom.style.lineHeight = ''; + }, 'id'); let inputDom = document.querySelectorAll('#loginform label') if (inputDom.length > 0) { if (document.getElementById('wwa-username-label')) { // WordPress 5.2- - document.getElementById('wwa-username-label').innerText = php_vars.email_login === 'true' ? php_vars.i18n_10 : php_vars.i18n_9; + document.getElementById('wwa-username-label').innerText = wwa_login_php_vars.email_login === 'true' ? wwa_login_php_vars.i18n_10 : wwa_login_php_vars.i18n_9; } else { - inputDom[0].innerText = php_vars.email_login === 'true' ? php_vars.i18n_10 : php_vars.i18n_9; + inputDom[0].innerText = wwa_login_php_vars.email_login === 'true' ? wwa_login_php_vars.i18n_10 : wwa_login_php_vars.i18n_9; } } + // Start Conditional UI when switching into WebAuthn mode + wwa_start_conditional_ui(); } } } @@ -276,13 +440,13 @@ function check() { return; } if (wwaSupported) { - if (document.getElementById('user_login').value === '' && php_vars.usernameless !== 'true') { + if (document.getElementById('user_login').value === '' && wwa_login_php_vars.usernameless !== 'true') { wwa_dom('login_error', (dom) => { dom.remove() }, 'id'); wwa_dom('p.message', (dom) => { dom.remove() }); if (document.querySelectorAll('#login > h1').length > 0) { let dom = document.createElement('div'); dom.id = 'login_error'; - dom.innerHTML = php_vars.i18n_11; + dom.innerHTML = wwa_login_php_vars.i18n_11; document.querySelectorAll('#login > h1')[0].parentNode.insertBefore(dom, document.querySelectorAll('#login > h1')[0].nextElementSibling) } // Shake the login form, code from WordPress @@ -293,22 +457,28 @@ function check() { wwa_shake(form, shake, 20); return; } + // Abort any pending Conditional UI before starting a new modal request + if (wwa_conditional_ui_abort) { + wwa_conditional_ui_abort.abort(); + wwa_conditional_ui_abort = null; + wwa_conditional_ui_active = false; + } wwa_dom('user_login', (dom) => { dom.readOnly = true }, 'id'); wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = true }); - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_3 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_3 }, 'class'); let request = wwa_ajax(); - request.get(php_vars.ajax_url, `?action=wwa_auth_start&user=${encodeURIComponent(document.getElementById('user_login').value)}&type=auth`, (rawData, status) => { + request.get(wwa_login_php_vars.ajax_url, `?action=wwa_auth_start&user=${encodeURIComponent(document.getElementById('user_login').value)}&type=auth`, (rawData, status) => { if (status) { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_4 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_4 }, 'class'); let data = rawData; try { data = JSON.parse(rawData); } catch (e) { console.warn(rawData); - if (php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 + php_vars.i18n_12 }, 'class'); + if (wwa_login_php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 + wwa_login_php_vars.i18n_12 }, 'class'); } else { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 }, 'class'); } wwa_dom('user_login', (dom) => { dom.readOnly = false }, 'id'); wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = false }); @@ -323,11 +493,11 @@ function check() { }); } - if (data.allowCredentials && php_vars.allow_authenticator_type && php_vars.allow_authenticator_type !== 'none') { + if (data.allowCredentials && wwa_login_php_vars.allow_authenticator_type && wwa_login_php_vars.allow_authenticator_type !== 'none') { for (let credential of data.allowCredentials) { - if (php_vars.allow_authenticator_type === 'cross-platform') { + if (wwa_login_php_vars.allow_authenticator_type === 'cross-platform') { credential.transports = ['usb', 'nfc', 'ble']; - } else if (php_vars.allow_authenticator_type === 'platform') { + } else if (wwa_login_php_vars.allow_authenticator_type === 'platform') { credential.transports = ['internal']; } } @@ -338,7 +508,7 @@ function check() { delete data.clientID; navigator.credentials.get({ 'publicKey': data }).then((credentialInfo) => { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_5 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_5 }, 'class'); return credentialInfo; }).then((data) => { const publicKeyCredential = { @@ -355,10 +525,10 @@ function check() { return publicKeyCredential; }).then(JSON.stringify).then((AuthenticatorResponse) => { let response = wwa_ajax(); - response.post(`${php_vars.ajax_url}?action=wwa_auth`, `data=${encodeURIComponent(window.btoa(AuthenticatorResponse))}&type=auth&clientid=${clientID}&user=${encodeURIComponent(document.getElementById('user_login').value)}&remember=${php_vars.remember_me === 'false' ? 'false' : (document.getElementById('rememberme') ? (document.getElementById('rememberme').checked ? 'true' : 'false') : 'false')}`, (data, status) => { + response.post(`${wwa_login_php_vars.ajax_url}?action=wwa_auth`, `data=${encodeURIComponent(window.btoa(AuthenticatorResponse))}&type=auth&clientid=${clientID}&user=${encodeURIComponent(document.getElementById('user_login').value)}&remember=${wwa_login_php_vars.remember_me === 'false' ? 'false' : (document.getElementById('rememberme') ? (document.getElementById('rememberme').checked ? 'true' : 'false') : 'false')}`, (data, status) => { if (status) { if (data === 'true') { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_6 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_6 }, 'class'); if (document.querySelectorAll('p.submit input[name="redirect_to"]').length > 0) { setTimeout(() => { window.location.href = document.querySelectorAll('p.submit input[name="redirect_to"]')[0].value; @@ -370,24 +540,24 @@ function check() { }, 200); } else { setTimeout(() => { - window.location.href = php_vars.admin_url + window.location.href = wwa_login_php_vars.admin_url }, 200); } } } else { - if (php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 + php_vars.i18n_12 }, 'class'); + if (wwa_login_php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 + wwa_login_php_vars.i18n_12 }, 'class'); } else { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 }, 'class'); } wwa_dom('user_login', (dom) => { dom.readOnly = false }, 'id'); wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = false }); } } else { - if (php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 + php_vars.i18n_12 }, 'class'); + if (wwa_login_php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 + wwa_login_php_vars.i18n_12 }, 'class'); } else { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 }, 'class'); } wwa_dom('user_login', (dom) => { dom.readOnly = false }, 'id'); wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = false }); @@ -395,23 +565,23 @@ function check() { }) }).catch((error) => { console.warn(error); - if (php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 + php_vars.i18n_12 }, 'class'); + if (wwa_login_php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 + wwa_login_php_vars.i18n_12 }, 'class'); } else { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 }, 'class'); } wwa_dom('user_login', (dom) => { dom.readOnly = false }, 'id'); wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = false }); }) } else { - if (php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 + php_vars.i18n_12 }, 'class'); + if (wwa_login_php_vars.usernameless === 'true' && document.getElementById('user_login').value === '') { + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 + wwa_login_php_vars.i18n_12 }, 'class'); } else { - wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = php_vars.i18n_7 }, 'class'); + wwa_dom('wp-webauthn-notice', (dom) => { dom.innerHTML = wwa_login_php_vars.i18n_7 }, 'class'); } wwa_dom('user_login', (dom) => { dom.readOnly = false }, 'id'); wwa_dom('#wp-webauthn-check, #wp-webauthn', (dom) => { dom.disabled = false }); } }) } -} \ No newline at end of file +} diff --git a/wp-content/plugins/wp-webauthn/js/profile.js b/wp-content/plugins/wp-webauthn/js/profile.js index 7af900e0..3684a47d 100644 --- a/wp-content/plugins/wp-webauthn/js/profile.js +++ b/wp-content/plugins/wp-webauthn/js/profile.js @@ -1,3 +1,5 @@ +const svg = ``; + // Whether the broswer supports WebAuthn if (window.PublicKeyCredential === undefined || navigator.credentials.create === undefined || typeof navigator.credentials.create !== 'function') { jQuery('#wwa-bind, #wwa-test').attr('disabled', 'disabled'); @@ -24,12 +26,13 @@ function updateList() { type: 'GET', data: { action: 'wwa_authenticator_list', - user_id: php_vars.user_id + user_id: php_vars.user_id, + _ajax_nonce: php_vars._ajax_nonce }, success: function (data) { if (typeof data === 'string') { console.warn(data); - jQuery('#wwa-authenticator-list').html(`${php_vars.i18n_8}`); + jQuery('#wwa-authenticator-list').html(`${php_vars.i18n_8}`); return; } if (data.length === 0) { @@ -38,7 +41,12 @@ function updateList() { } else { jQuery('.wwa-usernameless-th, .wwa-usernameless-td').hide(); } - jQuery('#wwa-authenticator-list').html(`${php_vars.i18n_17}`); + if (configs.show_authenticator_type === 'true') { + jQuery('.wwa-type-th, .wwa-type-td').show(); + } else { + jQuery('.wwa-type-th, .wwa-type-td').hide(); + } + jQuery('#wwa-authenticator-list').html(`${php_vars.i18n_17}`); jQuery('#wwa_usernameless_tip').text(''); jQuery('#wwa_usernameless_tip').hide(); jQuery('#wwa_type_tip').text(''); @@ -59,7 +67,7 @@ function updateList() { item_type_disabled = true; } } - htmlStr += `${item.name}${item.type === 'none' ? php_vars.i18n_9 : (item.type === 'platform' ? php_vars.i18n_10 : php_vars.i18n_11)}${item_type_disabled ? php_vars.i18n_29 : ''}${item.added}${item.last_used}${item.usernameless ? php_vars.i18n_24 + (configs.usernameless === 'true' ? '' : php_vars.i18n_26) : php_vars.i18n_25}${php_vars.i18n_20} | ${php_vars.i18n_12}`; + htmlStr += `${svg}${item.name}${configs.show_authenticator_type === 'true' ? `${item.type === 'none' ? php_vars.i18n_9 : (item.type === 'platform' ? php_vars.i18n_10 : php_vars.i18n_11)}${item_type_disabled ? php_vars.i18n_29 : ''}` : ''}${item.added}${item.last_used}${item.usernameless ? php_vars.i18n_24 + (configs.usernameless === 'true' ? '' : php_vars.i18n_26) : php_vars.i18n_25}${php_vars.i18n_20} | ${php_vars.i18n_12}`; } jQuery('#wwa-authenticator-list').html(htmlStr); if (has_usernameless || configs.usernameless === 'true') { @@ -67,6 +75,11 @@ function updateList() { } else { jQuery('.wwa-usernameless-th, .wwa-usernameless-td').hide(); } + if (configs.show_authenticator_type === 'true') { + jQuery('.wwa-type-th, .wwa-type-td').show(); + } else { + jQuery('.wwa-type-th, .wwa-type-td').hide(); + } if (has_usernameless && configs.usernameless !== 'true') { jQuery('#wwa_usernameless_tip').text(php_vars.i18n_27); jQuery('#wwa_usernameless_tip').show(); @@ -87,11 +100,23 @@ function updateList() { } }, error: function () { - jQuery('#wwa-authenticator-list').html(`${php_vars.i18n_8}`); + jQuery('#wwa-authenticator-list').html(`${php_vars.i18n_8}`); } }) } +// Compute current number of visible columns for colspan +function getColspan() { + let cols = 4; // Identifier, Registered, Last used, Action + if (jQuery('.wwa-type-th').length > 0 && jQuery('.wwa-type-th').css('display') !== 'none') { + cols++; + } + if (jQuery('.wwa-usernameless-th').length > 0 && jQuery('.wwa-usernameless-th').css('display') !== 'none') { + cols++; + } + return cols; +} + /** Code Base64URL into Base64 * * @param {string} input Base64URL coded string @@ -140,6 +165,10 @@ jQuery('.wwa-cancel').click((e) => { jQuery('#wwa-verify-block').hide(); }) +// Prevent WebAuthn registration fields from triggering WordPress's unsaved changes dialog. +// The form="wwa-registration" attribute on these inputs disassociates them from #your-profile, +// so they are excluded from jQuery serialize() comparisons in user-profile.js. + jQuery('#wwa_authenticator_name').keydown((e) => { if (e.keyCode === 13) { jQuery('#wwa-bind').trigger('click'); @@ -160,16 +189,19 @@ jQuery('#wwa-bind').click((e) => { jQuery('#wwa-bind').attr('disabled', 'disabled'); jQuery('#wwa_authenticator_name').attr('disabled', 'disabled'); jQuery('.wwa_authenticator_usernameless').attr('disabled', 'disabled'); - jQuery('#wwa_authenticator_type').attr('disabled', 'disabled'); + if (configs.show_authenticator_type === 'true') { + jQuery('#wwa_authenticator_type').attr('disabled', 'disabled'); + } jQuery.ajax({ url: php_vars.ajax_url, type: 'GET', data: { action: 'wwa_create', name: jQuery('#wwa_authenticator_name').val(), - type: jQuery('#wwa_authenticator_type').val(), + type: configs.show_authenticator_type === 'true' ? jQuery('#wwa_authenticator_type').val() : (configs.allow_authenticator_type !== 'none' ? configs.allow_authenticator_type : 'none'), usernameless: jQuery('.wwa_authenticator_usernameless:checked').val() ? jQuery('.wwa_authenticator_usernameless:checked').val() : 'false', - user_id: php_vars.user_id + user_id: php_vars.user_id, + _ajax_nonce: php_vars._ajax_nonce }, success: function (data) { if (typeof data === 'string') { @@ -178,7 +210,9 @@ jQuery('#wwa-bind').click((e) => { jQuery('#wwa-bind').removeAttr('disabled'); jQuery('#wwa_authenticator_name').removeAttr('disabled'); jQuery('.wwa_authenticator_usernameless').removeAttr('disabled'); - jQuery('#wwa_authenticator_type').removeAttr('disabled'); + if (configs.show_authenticator_type === 'true') { + jQuery('#wwa_authenticator_type').removeAttr('disabled'); + } updateList(); return; } @@ -241,10 +275,11 @@ jQuery('#wwa-bind').click((e) => { data: { data: window.btoa(AuthenticatorAttestationResponse), name: jQuery('#wwa_authenticator_name').val(), - type: jQuery('#wwa_authenticator_type').val(), + type: configs.show_authenticator_type === 'true' ? jQuery('#wwa_authenticator_type').val() : (configs.allow_authenticator_type !== 'none' ? configs.allow_authenticator_type : 'none'), usernameless: jQuery('.wwa_authenticator_usernameless:checked').val() ? jQuery('.wwa_authenticator_usernameless:checked').val() : 'false', clientid: clientID, - user_id: php_vars.user_id + user_id: php_vars.user_id, + _ajax_nonce: php_vars._ajax_nonce }, success: function (data) { if (data.trim() === 'true') { @@ -254,7 +289,9 @@ jQuery('#wwa-bind').click((e) => { jQuery('#wwa_authenticator_name').removeAttr('disabled'); jQuery('#wwa_authenticator_name').val(''); jQuery('.wwa_authenticator_usernameless').removeAttr('disabled'); - jQuery('#wwa_authenticator_type').removeAttr('disabled'); + if (configs.show_authenticator_type === 'true') { + jQuery('#wwa_authenticator_type').removeAttr('disabled'); + } updateList(); } else { // Register failed @@ -262,7 +299,9 @@ jQuery('#wwa-bind').click((e) => { jQuery('#wwa-bind').removeAttr('disabled'); jQuery('#wwa_authenticator_name').removeAttr('disabled'); jQuery('.wwa_authenticator_usernameless').removeAttr('disabled'); - jQuery('#wwa_authenticator_type').removeAttr('disabled'); + if (configs.show_authenticator_type === 'true') { + jQuery('#wwa_authenticator_type').removeAttr('disabled'); + } updateList(); } }, @@ -271,7 +310,9 @@ jQuery('#wwa-bind').click((e) => { jQuery('#wwa-bind').removeAttr('disabled'); jQuery('#wwa_authenticator_name').removeAttr('disabled'); jQuery('.wwa_authenticator_usernameless').removeAttr('disabled'); - jQuery('#wwa_authenticator_type').removeAttr('disabled'); + if (configs.show_authenticator_type === 'true') { + jQuery('#wwa_authenticator_type').removeAttr('disabled'); + } updateList(); } }) @@ -282,7 +323,9 @@ jQuery('#wwa-bind').click((e) => { jQuery('#wwa-bind').removeAttr('disabled'); jQuery('#wwa_authenticator_name').removeAttr('disabled'); jQuery('.wwa_authenticator_usernameless').removeAttr('disabled'); - jQuery('#wwa_authenticator_type').removeAttr('disabled'); + if (configs.show_authenticator_type === 'true') { + jQuery('#wwa_authenticator_type').removeAttr('disabled'); + } updateList(); }) }, @@ -291,7 +334,9 @@ jQuery('#wwa-bind').click((e) => { jQuery('#wwa-bind').removeAttr('disabled'); jQuery('#wwa_authenticator_name').removeAttr('disabled'); jQuery('.wwa_authenticator_usernameless').removeAttr('disabled'); - jQuery('#wwa_authenticator_type').removeAttr('disabled'); + if (configs.show_authenticator_type === 'true') { + jQuery('#wwa_authenticator_type').removeAttr('disabled'); + } updateList(); } }) @@ -428,7 +473,8 @@ function renameAuthenticator(id, name) { id: id, name: new_name, target: 'rename', - user_id: php_vars.user_id + user_id: php_vars.user_id, + _ajax_nonce: php_vars._ajax_nonce }, success: function () { updateList(); @@ -456,7 +502,8 @@ function removeAuthenticator(id, name) { action: 'wwa_modify_authenticator', id: id, target: 'remove', - user_id: php_vars.user_id + user_id: php_vars.user_id, + _ajax_nonce: php_vars._ajax_nonce }, success: function () { updateList(); diff --git a/wp-content/plugins/wp-webauthn/languages/template.pot b/wp-content/plugins/wp-webauthn/languages/template.pot index 76ac8dc6..bf893241 100644 --- a/wp-content/plugins/wp-webauthn/languages/template.pot +++ b/wp-content/plugins/wp-webauthn/languages/template.pot @@ -1,8 +1,8 @@ -# Copyright (C) 2024 Axton +# Copyright (C) 2026 Axton # This file is distributed under the GPLv3. msgid "" msgstr "" -"Project-Id-Version: WP-WebAuthn 1.3.2\n" +"Project-Id-Version: WP-WebAuthn 1.4.0\n" "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/wp-webauthn\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -42,620 +42,789 @@ msgstr "" msgid "https://axton.cc" msgstr "" -#: wwa-admin-content.php:6 +#: wwa-admin-content.php:11 +#: wwa-network-admin-content.php:98 msgid "User verification is disabled by default because some mobile devices do not support it (especially on Android devices). But we recommend you to enable it if possible to further secure your login." msgstr "" -#: wwa-admin-content.php:7 -#: wwa-admin-content.php:307 +#: wwa-admin-content.php:12 +#: wwa-admin-content.php:446 +#: wwa-network-admin-content.php:99 msgid "Log count: " msgstr "" -#: wwa-admin-content.php:8 -#: wwa-profile-content.php:14 -#: wwa-shortcodes.php:26 +#: wwa-admin-content.php:13 +#: wwa-network-admin-content.php:100 +#: wwa-profile-content.php:21 +#: wwa-shortcodes.php:34 msgid "Loading failed, maybe try refreshing?" msgstr "" -#: wwa-admin-content.php:16 +#: wwa-admin-content.php:21 msgid "PHP extension gmp doesn't seem to exist, rendering WP-WebAuthn unable to function." msgstr "" -#: wwa-admin-content.php:20 +#: wwa-admin-content.php:25 msgid "PHP extension mbstring doesn't seem to exist, rendering WP-WebAuthn unable to function." msgstr "" -#: wwa-admin-content.php:24 +#: wwa-admin-content.php:29 msgid "PHP extension sodium doesn't seem to exist, rendering WP-WebAuthn unable to function." msgstr "" -#: wwa-admin-content.php:28 +#: wwa-admin-content.php:33 msgid "WebAuthn features are restricted to websites in secure contexts. Please make sure your website is served over HTTPS or locally with localhost." msgstr "" -#: wwa-admin-content.php:131 +#: wwa-admin-content.php:186 +#: wwa-network-admin-content.php:114 msgid "Settings saved." msgstr "" -#: wwa-admin-content.php:133 +#: wwa-admin-content.php:188 msgid "Settings NOT saved." msgstr "" -#: wwa-admin-content.php:148 +#: wwa-admin-content.php:198 +msgid "Some settings are managed at the network level by the super administrator. %1$sConfigure network settings%2$s" +msgstr "" + +#: wwa-admin-content.php:218 +#: wwa-network-admin-content.php:123 msgid "Preferred login method" msgstr "" -#: wwa-admin-content.php:152 +#: wwa-admin-content.php:222 +#: wwa-network-admin-content.php:127 msgid "Prefer WebAuthn" msgstr "" -#: wwa-admin-content.php:153 +#: wwa-admin-content.php:223 +#: wwa-network-admin-content.php:128 msgid "Prefer password" msgstr "" -#: wwa-admin-content.php:154 -#: wwa-profile-content.php:82 +#: wwa-admin-content.php:224 +#: wwa-network-admin-content.php:129 +#: wwa-profile-content.php:94 msgid "WebAuthn Only" msgstr "" -#: wwa-admin-content.php:156 +#: wwa-admin-content.php:226 +#: wwa-network-admin-content.php:131 msgid "When using \"WebAuthn Only\", password login will be completely disabled. Please make sure your browser supports WebAuthn, otherwise you may unable to login.
User that doesn't have any registered authenticator (e.g. new user) will unable to login when using \"WebAuthn Only\".
When the browser does not support WebAuthn, the login method will default to password if password login is not disabled." msgstr "" -#: wwa-admin-content.php:160 +#: wwa-admin-content.php:231 +msgid "Terminology used for users" +msgstr "" + +#: wwa-admin-content.php:241 +msgctxt "Please note Passkey is a trademark owned by FIDO Alliance, please follow their guidelines for translation" +msgid "Passkey" +msgstr "" + +#: wwa-admin-content.php:242 +msgid "Choose how to name the authenticating technology to users.
Passkey is the brand name for this new way of digital authentication, while WebAuthn is the name of the technical standard under the hood." +msgstr "" + +#: wwa-admin-content.php:250 msgid "Website identifier" msgstr "" -#: wwa-admin-content.php:163 +#: wwa-admin-content.php:253 msgid "This identifier is for identification purpose only and DOES NOT affect the authentication process in anyway." msgstr "" -#: wwa-admin-content.php:167 +#: wwa-admin-content.php:257 msgid "Website domain" msgstr "" -#: wwa-admin-content.php:170 +#: wwa-admin-content.php:260 msgid "This field MUST be exactly the same with the current domain or parent domain." msgstr "" -#: wwa-admin-content.php:177 +#: wwa-admin-content.php:265 +#: wwa-network-admin-content.php:186 +msgid "Related origins" +msgstr "" + +#: wwa-admin-content.php:274 +#: wwa-network-admin-content.php:190 +msgid "Allow cross-site passkey usages (Related Origin Requests). May be useful for multi-site networks.
Enter one origin per line (e.g. https://example.com). Leave empty to disable." +msgstr "" + +#: wwa-admin-content.php:282 msgid "Allow to remember login" msgstr "" -#: wwa-admin-content.php:186 -#: wwa-admin-content.php:202 -#: wwa-admin-content.php:213 -#: wwa-admin-content.php:229 -#: wwa-admin-content.php:304 -#: wwa-profile-content.php:157 -#: wwa-shortcodes.php:135 +#: wwa-admin-content.php:291 +#: wwa-admin-content.php:307 +#: wwa-admin-content.php:319 +#: wwa-admin-content.php:335 +#: wwa-admin-content.php:368 +#: wwa-admin-content.php:443 +#: wwa-network-admin-content.php:142 +#: wwa-network-admin-content.php:153 +#: wwa-network-admin-content.php:176 +#: wwa-profile-content.php:175 +#: wwa-shortcodes.php:145 msgid "Enable" msgstr "" -#: wwa-admin-content.php:187 -#: wwa-admin-content.php:203 -#: wwa-admin-content.php:214 -#: wwa-admin-content.php:230 -#: wwa-admin-content.php:305 -#: wwa-profile-content.php:158 -#: wwa-shortcodes.php:135 +#: wwa-admin-content.php:292 +#: wwa-admin-content.php:308 +#: wwa-admin-content.php:320 +#: wwa-admin-content.php:336 +#: wwa-admin-content.php:369 +#: wwa-admin-content.php:444 +#: wwa-network-admin-content.php:143 +#: wwa-network-admin-content.php:154 +#: wwa-network-admin-content.php:177 +#: wwa-profile-content.php:176 +#: wwa-shortcodes.php:145 msgid "Disable" msgstr "" -#: wwa-admin-content.php:188 +#: wwa-admin-content.php:293 msgid "Show the 'Remember Me' checkbox beside the login form when using WebAuthn." msgstr "" -#: wwa-admin-content.php:193 +#: wwa-admin-content.php:298 msgid "Allow to login with email addresses" msgstr "" -#: wwa-admin-content.php:204 -msgid "Allow to find users via email addresses when logging in.
Note that if enabled attackers may be able to brute force the correspondences between email addresses and users." +#: wwa-admin-content.php:309 +msgid "Allow to find users via email addresses when logging in through WebAuthn.
Note that if enabled attackers may be able to brute force the correspondences between email addresses and users." msgstr "" -#: wwa-admin-content.php:209 +#: wwa-admin-content.php:315 +#: wwa-network-admin-content.php:138 msgid "Require user verification" msgstr "" -#: wwa-admin-content.php:215 +#: wwa-admin-content.php:321 +#: wwa-network-admin-content.php:144 msgid "User verification can improve security, but is not fully supported by mobile devices.
If you cannot register or verify your authenticators, please consider disabling user verification." msgstr "" -#: wwa-admin-content.php:220 +#: wwa-admin-content.php:326 +#: wwa-network-admin-content.php:149 msgid "Allow to login without username" msgstr "" -#: wwa-admin-content.php:231 +#: wwa-admin-content.php:337 +#: wwa-network-admin-content.php:155 msgid "Allow users to register authenticator with usernameless authentication feature and login without username.
User verification will be enabled automatically when authenticating with usernameless authentication feature.
Some authenticators and some browsers DO NOT support this feature." msgstr "" -#: wwa-admin-content.php:236 +#: wwa-admin-content.php:342 +#: wwa-network-admin-content.php:160 msgid "Allow a specific type of authenticator" msgstr "" -#: wwa-admin-content.php:245 -#: wwa-profile-content.php:15 -#: wwa-profile-content.php:138 -#: wwa-shortcodes.php:33 -#: wwa-shortcodes.php:127 +#: wwa-admin-content.php:351 +#: wwa-network-admin-content.php:164 +#: wwa-profile-content.php:22 +#: wwa-profile-content.php:155 +#: wwa-shortcodes.php:41 +#: wwa-shortcodes.php:135 msgid "Any" msgstr "" -#: wwa-admin-content.php:246 +#: wwa-admin-content.php:352 +#: wwa-network-admin-content.php:165 msgid "Platform (e.g. Passkey or built-in sensors)" msgstr "" -#: wwa-admin-content.php:247 -#: wwa-profile-content.php:140 -#: wwa-shortcodes.php:129 +#: wwa-admin-content.php:353 +#: wwa-network-admin-content.php:166 +#: wwa-profile-content.php:157 +#: wwa-shortcodes.php:137 msgid "Roaming (e.g. USB security keys)" msgstr "" -#: wwa-admin-content.php:249 +#: wwa-admin-content.php:355 +#: wwa-network-admin-content.php:168 msgid "If a type is selected, the browser will only prompt for authenticators of selected type when authenticating and user can only register authenticators of selected type." msgstr "" -#: wwa-admin-content.php:256 +#: wwa-admin-content.php:359 +#: wwa-network-admin-content.php:172 +msgid "Allow users to choose authenticator type" +msgstr "" + +#: wwa-admin-content.php:370 +#: wwa-network-admin-content.php:178 +msgid "When enabled, users can select the authenticator type when registering.
The \"Allow a specific type\" restriction above still applies regardless of this setting." +msgstr "" + +#: wwa-admin-content.php:379 msgid "Disable password reset for" msgstr "" -#: wwa-admin-content.php:265 +#: wwa-admin-content.php:388 msgid "Off" msgstr "" -#: wwa-admin-content.php:266 +#: wwa-admin-content.php:389 msgid "Everyone except administrators" msgstr "" -#: wwa-admin-content.php:267 +#: wwa-admin-content.php:390 msgid "Everyone" msgstr "" -#: wwa-admin-content.php:269 +#: wwa-admin-content.php:392 msgid "Disable the \"Set new password\" and \"Forgot password\" features, and remove the \"Forgot password\" link on the login page. This may be useful when enabling \"WebAuthn Only\".
If \"Everyone except administrators\" is selected, only administrators with the \"Edit user\" permission will be able to update passwords (for all users)." msgstr "" -#: wwa-admin-content.php:276 +#: wwa-admin-content.php:399 msgid "After User Registration" msgstr "" -#: wwa-admin-content.php:285 +#: wwa-admin-content.php:413 msgid "No action" msgstr "" -#: wwa-admin-content.php:286 +#: wwa-admin-content.php:414 msgid "Log user in and redirect to user's profile" msgstr "" -#: wwa-admin-content.php:288 +#: wwa-admin-content.php:416 +msgid "Send user an one-time login link via email" +msgstr "" + +#: wwa-admin-content.php:419 msgid "What to do when a new user registered.
By default, new users have to login manually after registration. If \"WebAuthn Only\" is enabled, they will not be able to login.
When using \"Log user in\", new users will be logged in automatically and redirected to their profile settings so that they can set up WebAuthn authenticators." msgstr "" -#: wwa-admin-content.php:295 +#: wwa-admin-content.php:423 +msgid "
When using \"Send login link\", an one-time login link will be automatically sent to the user's email adress. This will replace the default WordPress welcome email.
\"Send login link\" will work even if \"Allow user login by login link via email\" is disabled." +msgstr "" + +#: wwa-admin-content.php:434 msgid "Logging" msgstr "" -#: wwa-admin-content.php:307 +#: wwa-admin-content.php:446 msgid "Clear log" msgstr "" -#: wwa-admin-content.php:309 +#: wwa-admin-content.php:448 msgid "For debugging only. Enable only when needed.
Note: Logs may contain sensitive information." msgstr "" -#: wwa-admin-content.php:318 +#: wwa-admin-content.php:457 msgid "Log" msgstr "" -#: wwa-admin-content.php:320 +#: wwa-admin-content.php:459 msgid "Automatic update every 5 seconds." msgstr "" #. translators: %s: admin profile url -#: wwa-admin-content.php:325 +#: wwa-admin-content.php:464 msgid "To register a new authenticator or edit your authenticators, please go to your profile." msgstr "" -#: wwa-functions.php:159 -#: wwa-shortcodes.php:91 +#: wwa-functions.php:197 +#: wwa-shortcodes.php:99 msgid "Auth" msgstr "" -#: wwa-functions.php:160 -#: wwa-shortcodes.php:11 -#: wwa-shortcodes.php:81 -#: wwa-shortcodes.php:90 +#: wwa-functions.php:198 +#: wwa-shortcodes.php:19 +#: wwa-shortcodes.php:89 +#: wwa-shortcodes.php:98 msgid "Authenticate with WebAuthn" msgstr "" -#: wwa-functions.php:161 -#: wwa-shortcodes.php:12 +#: wwa-functions.php:198 +#: wwa-shortcodes.php:19 +#: wwa-shortcodes.php:89 +#: wwa-shortcodes.php:98 +msgid "Authenticate with a passkey" +msgstr "" + +#: wwa-functions.php:199 +#: wwa-shortcodes.php:20 msgid "Hold on..." msgstr "" -#: wwa-functions.php:162 -#: wwa-shortcodes.php:13 +#: wwa-functions.php:200 +#: wwa-shortcodes.php:21 msgid "Please proceed..." msgstr "" -#: wwa-functions.php:163 -#: wwa-shortcodes.php:14 +#: wwa-functions.php:201 +#: wwa-shortcodes.php:22 msgid "Authenticating..." msgstr "" -#: wwa-functions.php:164 -#: wwa-shortcodes.php:15 +#: wwa-functions.php:202 +#: wwa-shortcodes.php:23 msgid "Authenticated" msgstr "" -#: wwa-functions.php:165 -#: wwa-shortcodes.php:16 +#: wwa-functions.php:203 +#: wwa-shortcodes.php:24 msgid "Auth failed" msgstr "" -#: wwa-functions.php:166 +#: wwa-functions.php:204 msgid "It looks like your browser doesn't support WebAuthn, which means you may unable to login." msgstr "" -#: wwa-functions.php:167 -#: wwa-shortcodes.php:87 +#: wwa-functions.php:205 +#: wwa-shortcodes.php:95 msgid "Username" msgstr "" -#: wwa-functions.php:169 +#: wwa-functions.php:207 msgid "Error: The username field is empty." msgstr "" -#: wwa-functions.php:170 -#: wwa-shortcodes.php:42 +#: wwa-functions.php:208 +#: wwa-shortcodes.php:50 msgid "Try to enter the username" msgstr "" -#: wwa-functions.php:185 +#. translators: %s: 'WebAuthn' or 'passkey' +#: wwa-functions.php:210 +#: wwa-profile-content.php:41 +#: wwa-profile-content.php:87 +msgid "Passkey" +msgstr "" + +#: wwa-functions.php:225 msgid "Logging in with password has been disabled by the site manager." msgstr "" -#: wwa-functions.php:191 +#: wwa-functions.php:231 msgid "Logging in with password has been disabled for this account." msgstr "" -#. translators: %s: 'the site' or 'your account', and admin profile url -#: wwa-functions.php:276 -msgid "Logging in with password has been disabled for %s but you haven't register any WebAuthn authenticator yet. You may unable to login again once you log out. Register" -msgstr "" - -#. translators: %s: 'the site' or 'your account', and admin profile url -#. translators: %s: 'the site' or 'your account' -#: wwa-functions.php:276 -#: wwa-functions.php:322 +#: wwa-functions.php:305 +#: wwa-functions.php:341 msgid "the site" msgstr "" -#. translators: %s: 'the site' or 'your account', and admin profile url -#: wwa-functions.php:276 +#: wwa-functions.php:305 msgid "your account" msgstr "" -#. translators: %s: 'the site' or 'your account' -#: wwa-functions.php:322 -msgid "Logging in with password has been disabled for %s but this account haven't register any WebAuthn authenticator yet. This user may unable to login." +#: wwa-functions.php:306 +#: wwa-functions.php:342 +msgid "WebAuthn authenticator" msgstr "" -#. translators: %s: 'the site' or 'your account' -#: wwa-functions.php:322 +#: wwa-functions.php:306 +#: wwa-functions.php:342 +msgid "passkey" +msgstr "" + +#: wwa-functions.php:309 +msgid "Logging in with password has been disabled for %1$s but you haven't register any %2$s on the current site yet. You may unable to login again once you log out. Register" +msgstr "" + +#. translators: %s: 'WebAuthn authenticators' or 'Passkeys' +#: wwa-functions.php:312 +#: wwa-functions.php:348 +msgid "%s registered on other sites within this network may also be used to log in." +msgstr "" + +#. translators: %s: 'WebAuthn authenticators' or 'Passkeys' +#: wwa-functions.php:312 +#: wwa-functions.php:348 +msgid "WebAuthn authenticators" +msgstr "" + +#. translators: %s: 'WebAuthn authenticators' or 'Passkeys' +#: wwa-functions.php:312 +#: wwa-functions.php:348 +#: wwa-profile-content.php:54 +msgid "Passkeys" +msgstr "" + +#: wwa-functions.php:341 msgid "this account" msgstr "" -#: wwa-functions.php:350 +#: wwa-functions.php:345 +msgid "Logging in with password has been disabled for %1$s but this account haven't register any %2$s on the current site yet. This user may unable to login." +msgstr "" + +#: wwa-functions.php:377 msgid "Settings" msgstr "" -#: wwa-functions.php:358 +#: wwa-functions.php:385 +#: wwa-network-admin-content.php:111 +msgid "Network Settings" +msgstr "" + +#: wwa-functions.php:395 msgid "GitHub" msgstr "" -#: wwa-functions.php:359 +#: wwa-functions.php:396 msgid "Documentation" msgstr "" -#: wwa-profile-content.php:7 +#: wwa-network-admin-content.php:117 +msgid "These settings apply to all sites in the network." +msgstr "" + +#: wwa-profile-content.php:14 msgid "Initializing..." msgstr "" -#: wwa-profile-content.php:8 -#: wwa-shortcodes.php:37 +#: wwa-profile-content.php:15 +#: wwa-shortcodes.php:45 msgid "Please follow instructions to finish registration..." msgstr "" -#: wwa-profile-content.php:9 -#: wwa-shortcodes.php:38 +#: wwa-profile-content.php:16 +#: wwa-shortcodes.php:46 msgctxt "action" msgid "Registered" msgstr "" -#: wwa-profile-content.php:10 -#: wwa-shortcodes.php:39 +#: wwa-profile-content.php:17 +#: wwa-shortcodes.php:47 msgid "Registration failed" msgstr "" -#: wwa-profile-content.php:11 -#: wwa-shortcodes.php:40 +#: wwa-profile-content.php:18 +#: wwa-shortcodes.php:48 msgid "Your browser does not support WebAuthn" msgstr "" -#: wwa-profile-content.php:12 -#: wwa-shortcodes.php:41 +#: wwa-profile-content.php:19 +#: wwa-shortcodes.php:49 msgid "Registrating..." msgstr "" -#: wwa-profile-content.php:13 -#: wwa-shortcodes.php:21 +#: wwa-profile-content.php:20 +#: wwa-shortcodes.php:29 msgid "Please enter the authenticator identifier" msgstr "" -#: wwa-profile-content.php:16 -#: wwa-shortcodes.php:34 +#: wwa-profile-content.php:23 +#: wwa-shortcodes.php:42 msgid "Platform authenticator" msgstr "" -#: wwa-profile-content.php:17 -#: wwa-shortcodes.php:35 +#: wwa-profile-content.php:24 +#: wwa-shortcodes.php:43 msgid "Roaming authenticator" msgstr "" -#: wwa-profile-content.php:18 -#: wwa-shortcodes.php:36 +#: wwa-profile-content.php:25 +#: wwa-shortcodes.php:44 msgid "Remove" msgstr "" -#: wwa-profile-content.php:19 -#: wwa-shortcodes.php:22 +#: wwa-profile-content.php:26 +#: wwa-shortcodes.php:30 msgid "Please follow instructions to finish verification..." msgstr "" -#: wwa-profile-content.php:20 -#: wwa-shortcodes.php:23 +#: wwa-profile-content.php:27 +#: wwa-shortcodes.php:31 msgid "Verifying..." msgstr "" -#: wwa-profile-content.php:21 -#: wwa-shortcodes.php:24 +#: wwa-profile-content.php:28 +#: wwa-shortcodes.php:32 msgid "Verification failed" msgstr "" -#: wwa-profile-content.php:22 -#: wwa-shortcodes.php:25 +#: wwa-profile-content.php:29 +#: wwa-shortcodes.php:33 msgid "Verification passed! You can now log in through WebAuthn" msgstr "" -#: wwa-profile-content.php:23 -#: wwa-shortcodes.php:32 -msgid "No registered authenticators" -msgstr "" - -#: wwa-profile-content.php:24 -#: wwa-shortcodes.php:27 -msgid "Confirm removal of authenticator: " -msgstr "" - -#: wwa-profile-content.php:25 -#: wwa-shortcodes.php:28 -msgid "Removing..." -msgstr "" - -#: wwa-profile-content.php:26 -#: wwa-shortcodes.php:29 -msgid "Rename" -msgstr "" - -#: wwa-profile-content.php:27 -#: wwa-shortcodes.php:30 -msgid "Rename the authenticator" -msgstr "" - -#: wwa-profile-content.php:28 -#: wwa-shortcodes.php:31 -msgid "Renaming..." -msgstr "" - #: wwa-profile-content.php:29 -#: wwa-shortcodes.php:10 -msgid "Ready" +msgid "Verification passed! You can now log in with this passkey" msgstr "" #: wwa-profile-content.php:30 -#: wwa-shortcodes.php:17 -msgid "No" +#: wwa-shortcodes.php:40 +msgid "No registered authenticators" msgstr "" #: wwa-profile-content.php:31 -#: wwa-shortcodes.php:18 -msgid " (Unavailable)" +#: wwa-shortcodes.php:35 +msgid "Confirm removal of authenticator: " msgstr "" #: wwa-profile-content.php:32 -#: wwa-shortcodes.php:19 -msgid "The site administrator has disabled usernameless login feature." +#: wwa-shortcodes.php:36 +msgid "Removing..." msgstr "" #: wwa-profile-content.php:33 -#: wwa-shortcodes.php:43 -msgid "After removing this authenticator, you will not be able to login with WebAuthn" +#: wwa-shortcodes.php:37 +msgid "Rename" msgstr "" #: wwa-profile-content.php:34 -#: wwa-shortcodes.php:44 -msgid " (Disabled)" +#: wwa-shortcodes.php:38 +msgid "Rename the authenticator" msgstr "" #: wwa-profile-content.php:35 -#: wwa-shortcodes.php:45 -msgid "The site administrator only allow platform authenticators currently." +#: wwa-shortcodes.php:39 +msgid "Renaming..." msgstr "" #: wwa-profile-content.php:36 -#: wwa-shortcodes.php:46 +#: wwa-shortcodes.php:18 +msgid "Ready" +msgstr "" + +#: wwa-profile-content.php:37 +#: wwa-shortcodes.php:25 +msgid "No" +msgstr "" + +#: wwa-profile-content.php:38 +#: wwa-shortcodes.php:26 +msgid " (Unavailable)" +msgstr "" + +#: wwa-profile-content.php:39 +#: wwa-shortcodes.php:27 +msgid "The site administrator has disabled usernameless login feature." +msgstr "" + +#. translators: %s: 'WebAuthn' or 'passkey' +#: wwa-profile-content.php:41 +msgid "After removing this authenticator, you will not be able to login with %s" +msgstr "" + +#: wwa-profile-content.php:42 +#: wwa-shortcodes.php:52 +msgid " (Disabled)" +msgstr "" + +#: wwa-profile-content.php:43 +#: wwa-shortcodes.php:53 +msgid "The site administrator only allow platform authenticators currently." +msgstr "" + +#: wwa-profile-content.php:44 +#: wwa-shortcodes.php:54 msgid "The site administrator only allow roaming authenticators currently." msgstr "" -#: wwa-profile-content.php:64 -msgid "You've successfully registered! Now you can register your authenticators below." +#: wwa-profile-content.php:70 +#: wwa-profile-content.php:142 +msgid "authenticators" msgstr "" -#: wwa-profile-content.php:76 -msgid "This site is not correctly configured to use WebAuthn. Please contact the site administrator." +#: wwa-profile-content.php:70 +#: wwa-profile-content.php:142 +msgid "passkeys" msgstr "" -#: wwa-profile-content.php:86 -msgid "Disable password login for this account" +#. translators: %1$s: 'authenticators' or 'passkeys' +#: wwa-profile-content.php:72 +msgid "You've successfully registered! Now you can register your %1$s below." msgstr "" -#: wwa-profile-content.php:88 -msgid "When checked, password login will be completely disabled. Please make sure your browser supports WebAuthn and you have a registered authenticator, otherwise you may unable to login." +#. translators: %s: 'WebAuthn' or 'passkey' +#: wwa-profile-content.php:87 +msgid "This site is not correctly configured to use %s. Please contact the site administrator." msgstr "" -#: wwa-profile-content.php:88 -msgid "The site administrator has disabled password login for the whole site." -msgstr "" - -#: wwa-profile-content.php:92 -msgid "Registered WebAuthn Authenticators" -msgstr "" - -#: wwa-profile-content.php:97 -#: wwa-profile-content.php:112 -#: wwa-shortcodes.php:175 -#: wwa-shortcodes.php:177 -msgid "Identifier" +#: wwa-profile-content.php:94 +msgid "Passkey Only" msgstr "" #: wwa-profile-content.php:98 -#: wwa-profile-content.php:113 -#: wwa-shortcodes.php:175 -#: wwa-shortcodes.php:177 +msgid "Disable password login for this account" +msgstr "" + +#: wwa-profile-content.php:100 +msgid "When checked, password login will be completely disabled. Please make sure your browser supports WebAuthn and you have a registered authenticator, otherwise you may unable to login." +msgstr "" + +#: wwa-profile-content.php:100 +msgid "When checked, password login will be completely disabled. Please make sure you have a registered passkey, otherwise you may unable to login." +msgstr "" + +#: wwa-profile-content.php:100 +msgid "This setting applies to your account across all sites in the network." +msgstr "" + +#: wwa-profile-content.php:100 +msgid "The site administrator has disabled password login for the whole site." +msgstr "" + +#: wwa-profile-content.php:104 +msgid "Registered WebAuthn Authenticators" +msgstr "" + +#: wwa-profile-content.php:104 +msgid "Registered Passkeys" +msgstr "" + +#: wwa-profile-content.php:109 +#: wwa-profile-content.php:124 +#: wwa-shortcodes.php:188 +#: wwa-shortcodes.php:190 +msgid "Identifier" +msgstr "" + +#: wwa-profile-content.php:110 +#: wwa-profile-content.php:125 +#: wwa-shortcodes.php:186 msgid "Type" msgstr "" -#: wwa-profile-content.php:99 -#: wwa-profile-content.php:114 -#: wwa-shortcodes.php:175 -#: wwa-shortcodes.php:177 +#: wwa-profile-content.php:111 +#: wwa-profile-content.php:126 +#: wwa-shortcodes.php:188 +#: wwa-shortcodes.php:190 msgctxt "time" msgid "Registered" msgstr "" -#: wwa-profile-content.php:100 -#: wwa-profile-content.php:115 -#: wwa-shortcodes.php:175 -#: wwa-shortcodes.php:177 +#: wwa-profile-content.php:112 +#: wwa-profile-content.php:127 +#: wwa-shortcodes.php:188 +#: wwa-shortcodes.php:190 msgid "Last used" msgstr "" -#: wwa-profile-content.php:101 -#: wwa-profile-content.php:116 -#: wwa-shortcodes.php:175 -#: wwa-shortcodes.php:177 +#: wwa-profile-content.php:113 +#: wwa-profile-content.php:128 +#: wwa-shortcodes.php:188 +#: wwa-shortcodes.php:190 msgid "Usernameless" msgstr "" -#: wwa-profile-content.php:102 -#: wwa-profile-content.php:117 -#: wwa-shortcodes.php:175 -#: wwa-shortcodes.php:177 +#: wwa-profile-content.php:114 +#: wwa-profile-content.php:129 +#: wwa-shortcodes.php:188 +#: wwa-shortcodes.php:190 msgid "Action" msgstr "" -#: wwa-profile-content.php:107 -#: wwa-shortcodes.php:176 +#: wwa-profile-content.php:119 +#: wwa-shortcodes.php:189 msgid "Loading..." msgstr "" -#: wwa-profile-content.php:124 -#: wwa-profile-content.php:127 +#: wwa-profile-content.php:136 +#: wwa-profile-content.php:139 msgid "Register New Authenticator" msgstr "" -#: wwa-profile-content.php:124 -#: wwa-profile-content.php:169 +#: wwa-profile-content.php:136 +#: wwa-profile-content.php:139 +msgid "Register New Passkey" +msgstr "" + +#: wwa-profile-content.php:136 +#: wwa-profile-content.php:187 msgid "Verify Authenticator" msgstr "" -#. translators: %s: user login name -#: wwa-profile-content.php:129 -msgid "You are about to associate an authenticator with the current account %s.
You can register multiple authenticators for an account." +#: wwa-profile-content.php:136 +#: wwa-profile-content.php:187 +msgid "Verify Passkey" msgstr "" -#: wwa-profile-content.php:132 -#: wwa-shortcodes.php:125 -msgid "Type of authenticator" +#: wwa-profile-content.php:141 +msgid "an authenticator" msgstr "" -#: wwa-profile-content.php:139 -#: wwa-shortcodes.php:128 -msgid "Platform (e.g. built-in fingerprint sensors)" +#: wwa-profile-content.php:141 +msgid "a passkey" msgstr "" -#: wwa-profile-content.php:142 -#: wwa-shortcodes.php:131 -msgid "If a type is selected, the browser will only prompt for authenticators of selected type.
Regardless of the type, you can only log in with the very same authenticators you've registered." -msgstr "" - -#: wwa-profile-content.php:146 -msgid "Authenticator Identifier" +#: wwa-profile-content.php:145 +msgid "You are about to associate %1$s with the current account %2$s.
You can register multiple %3$s for an account." msgstr "" #: wwa-profile-content.php:149 -#: wwa-shortcodes.php:134 -msgid "An easily identifiable name for the authenticator. DOES NOT affect the authentication process in anyway." +#: wwa-shortcodes.php:133 +msgid "Type of authenticator" msgstr "" -#: wwa-profile-content.php:154 -#: wwa-shortcodes.php:135 -msgid "Login without username" +#: wwa-profile-content.php:156 +#: wwa-shortcodes.php:136 +msgid "Platform (e.g. built-in fingerprint sensors)" msgstr "" #: wwa-profile-content.php:159 -#: wwa-shortcodes.php:135 +#: wwa-shortcodes.php:139 +msgid "If a type is selected, the browser will only prompt for authenticators of selected type.
Regardless of the type, you can only log in with the very same authenticators you've registered." +msgstr "" + +#: wwa-profile-content.php:164 +msgid "Authenticator Identifier" +msgstr "" + +#: wwa-profile-content.php:167 +#: wwa-shortcodes.php:144 +msgid "An easily identifiable name for the authenticator. DOES NOT affect the authentication process in anyway." +msgstr "" + +#: wwa-profile-content.php:172 +#: wwa-shortcodes.php:145 +msgid "Login without username" +msgstr "" + +#: wwa-profile-content.php:177 +#: wwa-shortcodes.php:145 msgid "If registered authenticator with this feature, you can login without enter your username.
Some authenticators like U2F-only authenticators and some browsers DO NOT support this feature.
A record will be stored in the authenticator permanently untill you reset it." msgstr "" -#: wwa-profile-content.php:165 +#: wwa-profile-content.php:183 msgid "Start Registration" msgstr "" -#: wwa-profile-content.php:170 +#: wwa-profile-content.php:188 msgid "Click Test Login to verify that the registered authenticators are working." msgstr "" -#: wwa-profile-content.php:171 -#: wwa-shortcodes.php:163 +#: wwa-profile-content.php:189 +#: wwa-shortcodes.php:173 msgid "Test Login" msgstr "" -#: wwa-profile-content.php:173 -#: wwa-shortcodes.php:163 +#: wwa-profile-content.php:191 +#: wwa-shortcodes.php:173 msgid "Test Login (usernameless)" msgstr "" -#: wwa-shortcodes.php:20 +#: wwa-shortcodes.php:28 msgid "Error: The username field is empty." msgstr "" -#: wwa-shortcodes.php:91 +#: wwa-shortcodes.php:51 +msgid "After removing this authenticator, you will not be able to login with WebAuthn" +msgstr "" + +#: wwa-shortcodes.php:99 msgid "Authenticate with password" msgstr "" -#: wwa-shortcodes.php:110 -#: wwa-shortcodes.php:152 -#: wwa-shortcodes.php:185 +#: wwa-shortcodes.php:118 +#: wwa-shortcodes.php:162 +#: wwa-shortcodes.php:198 msgid "You haven't logged in yet." msgstr "" -#: wwa-shortcodes.php:132 +#: wwa-shortcodes.php:142 msgid "Authenticator identifier" msgstr "" -#: wwa-shortcodes.php:136 +#: wwa-shortcodes.php:146 msgid "Start registration" msgstr "" diff --git a/wp-content/plugins/wp-webauthn/readme.txt b/wp-content/plugins/wp-webauthn/readme.txt index d58fe3a0..6caacf29 100644 --- a/wp-content/plugins/wp-webauthn/readme.txt +++ b/wp-content/plugins/wp-webauthn/readme.txt @@ -1,11 +1,11 @@ === WP-WebAuthn === Contributors: axton Donate link: https://flyhigher.top/about -Tags: u2f, webauthn, passkey, login, security +Tags: webauthn, passkey, login, security, fido, password, faceid Requires at least: 5.0 -Tested up to: 6.6 -Stable tag: 1.3.4 -Requires PHP: 7.2 +Tested up to: 6.9 +Stable tag: 1.4.1 +Requires PHP: 7.4 License: GPLv3 License URI: https://www.gnu.org/licenses/gpl-3.0.html @@ -25,13 +25,15 @@ This plugin has 4 built-in shortcodes and 4 built-in Gutenberg blocks, so you ca Please refer to the [documentation](http://doc.flyhigher.top/wp-webauthn) before using the plugin. +This plugin currently has *BETA* multisite support, if you find any issue in multisite, feel free to [open an issue](https://github.com/yrccondor/wp-webauthn/issues/new) on GitHub. + **PHP extensions gmp and mbstring are required.** **WebAuthn requires HTTPS connection or `localhost` to function normally.** You can contribute to this plugin on [GitHub](https://github.com/yrccondor/wp-webauthn). -Please note that this plugin does NOT support Internet Explorer (including IE 11). To use FaceID or TouchID, you need to use iOS/iPadOS 14+. +> Please note that this plugin does NOT support Internet Explorer (including IE 11). To use FaceID or TouchID, you need to use iOS/iPadOS 14+. = Security and Privacy = @@ -80,6 +82,18 @@ To use FaceID or TouchID, you need to use iOS/iPadOS 14+. == Changelog == += 1.4.1 = +Fix: Error when saving settings + += 1.4.0 = +Add: "Passkey" terminology option +Add: Multisite support (beta) +Update: Improved Passkey experience on login page +Update: Minimum PHP version raised to 7.4 +Update: Translations +Update: Third party libraries +Chore: Updated role checking + = 1.3.4 = Fix: Make sure AJAX works with extra spaces/new lines Note: We'll soon drop support for PHP 7.4 and below. Please upgrade your PHP version to 8.0+. @@ -106,68 +120,7 @@ Fix: 2FA compatibility Update: Translations Update: Third party libraries -= 1.2.8 = -Fix: privilege check for admin panel - -= 1.2.7 = -Add: Now a security warning will be shown if user verification is disabled -Fix: Style broken with some locales -Fix: privilege check for admin panel (thanks to @vanpop) -Update: Third party libraries - -= 1.2.6 = -Update: Third party libraries - -= 1.2.5 = -Update: German translation (thanks to niiconn) -Fix: HTTPS check - -= 1.2.4 = -Add: French translation (thanks to Spomky) and Turkish translate (thanks to Sn0bzy) -Fix: HTTPS check -Update: Existing translations -Update: Third party libraries - -= 1.2.3 = -Feat: Avoid locking users out if WebAuthn is not available -Update: translations -Update: Third party libraries - -= 1.2.2 = -Fix: Cannot access to js files in apache 2.4+ - -= 1.2.1 = -Feat: Allow to disable password login completely -Feat: Now we use WordPress transients instead of PHP sessions -Feat: Move register related settings to user's profile -Feat: Gutenberg block support -Feat: Traditional Chinese (Hong Kong) & Traditional Chinese (Taiwan) translation -Update: Chinese translation -Update: Third-party libraries - -= 1.1.0 = -Add: Allow to remember login option -Add: Only allow a specific type of authenticator option -Fix: Toggle button may not working in login form -Update: Chinese translation -Update: Third-party libraries - == Upgrade Notice == -= 1.2.5 = -Improvred HTTPS checking and updated German translation (by niiconn) - -= 1.2.4 = -Improvred HTTPS checking and added new translations - -= 1.2.3 = -Avoid locking users out if WebAuthn is not available and update translations - -= 1.2.2 = -Fixed a problem that js files were broken in apache 2.4+ - -= 1.2.1 = -New features, bug fixing and new translations - -= 1.1.0 = -2 new features & bug fixing += 1.4.1 = +New "Passkey" terminology option, multisite support (beta), improved Passkey experience and more diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/autoload.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/autoload.php index 710379cf..caee0514 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/autoload.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/autoload.php @@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) { echo $err; } } - trigger_error( - $err, - E_USER_ERROR - ); + throw new RuntimeException($err); } require_once __DIR__ . '/composer/autoload_real.php'; diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/.github/workflows/ci.yml b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/.github/workflows/ci.yml deleted file mode 100644 index 7003ff49..00000000 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/.github/workflows/ci.yml +++ /dev/null @@ -1,64 +0,0 @@ -on: [push, pull_request] -name: CI -jobs: - tests: - name: Tests - runs-on: ubuntu-latest - strategy: - matrix: - php-versions: ['7.0', '7.1', '7.2', '7.3', '7.4', '8.0', '8.1'] - - steps: - - name: Checkout - uses: actions/checkout@v1 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: "cs2pr" - - - name: "Cache dependencies installed with composer" - uses: "actions/cache@v1" - with: - path: "~/.composer/cache" - key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" - restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - - - name: "Composer" - run: "composer update --prefer-stable" - - - name: "PHPUnit" - run: "php vendor/bin/phpunit" - -# lint: -# name: Lint -# runs-on: ubuntu-latest - -# steps: -# - name: Checkout -# uses: actions/checkout@v1 - -# - name: Setup PHP -# uses: shivammathur/setup-php@v2 -# with: -# php-version: 7.4 - -# - name: "Cache dependencies installed with composer" -# uses: "actions/cache@v1" -# with: -# path: "~/.composer/cache" -# key: "php-${{ matrix.php-version }}-composer-locked-${{ hashFiles('composer.lock') }}" -# restore-keys: "php-${{ matrix.php-version }}-composer-locked-" - -# - name: "Composer" -# run: "composer update --prefer-stable" - -# - name: "assert:cs-lint" -# run: "composer assert:cs-lint" - -# - name: "assert:sa-code" -# run: "composer assert:sa-code" - -# - name: "assert:sa-tests" -# run: "composer assert:sa-tests" diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/composer.json b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/composer.json index 1485a008..cbf18a95 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/composer.json +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/composer.json @@ -23,7 +23,7 @@ "sort-packages": true }, "require": { - "php": "^7.0 || ^8.0", + "php": "^7.1 || ^8.0", "ext-simplexml": "*", "ext-mbstring": "*", "ext-ctype": "*", diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assert.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assert.php index 3614b345..201bce57 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assert.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assert.php @@ -42,7 +42,7 @@ abstract class Assert * The assertion chain can be stateful, that means be careful when you reuse * it. You should never pass around the chain. */ - public static function that($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain + public static function that($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain { $assertionChain = new AssertionChain($value, $defaultMessage, $defaultPropertyPath); @@ -55,7 +55,7 @@ abstract class Assert * @param mixed $values * @param string|callable|null $defaultMessage */ - public static function thatAll($values, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain + public static function thatAll($values, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain { return static::that($values, $defaultMessage, $defaultPropertyPath)->all(); } @@ -66,7 +66,7 @@ abstract class Assert * @param mixed $value * @param string|callable|null $defaultMessage */ - public static function thatNullOr($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain + public static function thatNullOr($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain { return static::that($value, $defaultMessage, $defaultPropertyPath)->nullOr(); } diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assertion.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assertion.php index 243e64d2..81bc9780 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assertion.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/Assertion.php @@ -307,7 +307,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function eq($value, $value2, $message = null, string $propertyPath = null): bool + public static function eq($value, $value2, $message = null, ?string $propertyPath = null): bool { if ($value != $value2) { $message = \sprintf( @@ -331,7 +331,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function eqArraySubset($value, $value2, $message = null, string $propertyPath = null): bool + public static function eqArraySubset($value, $value2, $message = null, ?string $propertyPath = null): bool { static::isArray($value, $message, $propertyPath); static::isArray($value2, $message, $propertyPath); @@ -358,7 +358,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function same($value, $value2, $message = null, string $propertyPath = null): bool + public static function same($value, $value2, $message = null, ?string $propertyPath = null): bool { if ($value !== $value2) { $message = \sprintf( @@ -382,7 +382,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notEq($value1, $value2, $message = null, string $propertyPath = null): bool + public static function notEq($value1, $value2, $message = null, ?string $propertyPath = null): bool { if ($value1 == $value2) { $message = \sprintf( @@ -412,7 +412,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notSame($value1, $value2, $message = null, string $propertyPath = null): bool + public static function notSame($value1, $value2, $message = null, ?string $propertyPath = null): bool { if ($value1 === $value2) { $message = \sprintf( @@ -434,7 +434,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notInArray($value, array $choices, $message = null, string $propertyPath = null): bool + public static function notInArray($value, array $choices, $message = null, ?string $propertyPath = null): bool { if (true === \in_array($value, $choices)) { $message = \sprintf( @@ -461,7 +461,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function integer($value, $message = null, string $propertyPath = null): bool + public static function integer($value, $message = null, ?string $propertyPath = null): bool { if (!\is_int($value)) { $message = \sprintf( @@ -488,7 +488,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function float($value, $message = null, string $propertyPath = null): bool + public static function float($value, $message = null, ?string $propertyPath = null): bool { if (!\is_float($value)) { $message = \sprintf( @@ -515,7 +515,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function digit($value, $message = null, string $propertyPath = null): bool + public static function digit($value, $message = null, ?string $propertyPath = null): bool { if (!\ctype_digit((string)$value)) { $message = \sprintf( @@ -537,7 +537,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function integerish($value, $message = null, string $propertyPath = null): bool + public static function integerish($value, $message = null, ?string $propertyPath = null): bool { if ( \is_resource($value) || @@ -577,7 +577,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function boolean($value, $message = null, string $propertyPath = null): bool + public static function boolean($value, $message = null, ?string $propertyPath = null): bool { if (!\is_bool($value)) { $message = \sprintf( @@ -604,7 +604,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function scalar($value, $message = null, string $propertyPath = null): bool + public static function scalar($value, $message = null, ?string $propertyPath = null): bool { if (!\is_scalar($value)) { $message = \sprintf( @@ -631,7 +631,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notEmpty($value, $message = null, string $propertyPath = null): bool + public static function notEmpty($value, $message = null, ?string $propertyPath = null): bool { if (empty($value)) { $message = \sprintf( @@ -658,7 +658,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function noContent($value, $message = null, string $propertyPath = null): bool + public static function noContent($value, $message = null, ?string $propertyPath = null): bool { if (!empty($value)) { $message = \sprintf( @@ -683,7 +683,7 @@ class Assertion * * @return bool */ - public static function null($value, $message = null, string $propertyPath = null): bool + public static function null($value, $message = null, ?string $propertyPath = null): bool { if (null !== $value) { $message = \sprintf( @@ -710,7 +710,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notNull($value, $message = null, string $propertyPath = null): bool + public static function notNull($value, $message = null, ?string $propertyPath = null): bool { if (null === $value) { $message = \sprintf( @@ -737,7 +737,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function string($value, $message = null, string $propertyPath = null) + public static function string($value, $message = null, ?string $propertyPath = null) { if (!\is_string($value)) { $message = \sprintf( @@ -766,7 +766,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function regex($value, $pattern, $message = null, string $propertyPath = null): bool + public static function regex($value, $pattern, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); @@ -794,7 +794,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notRegex($value, $pattern, $message = null, string $propertyPath = null): bool + public static function notRegex($value, $pattern, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); @@ -825,7 +825,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function length($value, $length, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function length($value, $length, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($value, $message, $propertyPath); @@ -858,7 +858,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function minLength($value, $minLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function minLength($value, $minLength, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($value, $message, $propertyPath); @@ -891,7 +891,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function maxLength($value, $maxLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function maxLength($value, $maxLength, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($value, $message, $propertyPath); @@ -925,7 +925,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function betweenLength($value, $minLength, $maxLength, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function betweenLength($value, $minLength, $maxLength, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($value, $message, $propertyPath); static::minLength($value, $minLength, $message, $propertyPath, $encoding); @@ -949,7 +949,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function startsWith($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function startsWith($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($string, $message, $propertyPath); @@ -981,7 +981,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function endsWith($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function endsWith($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($string, $message, $propertyPath); @@ -1015,7 +1015,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function contains($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function contains($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($string, $message, $propertyPath); @@ -1047,7 +1047,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notContains($string, $needle, $message = null, string $propertyPath = null, $encoding = 'utf8'): bool + public static function notContains($string, $needle, $message = null, ?string $propertyPath = null, $encoding = 'utf8'): bool { static::string($string, $message, $propertyPath); @@ -1072,7 +1072,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function choice($value, array $choices, $message = null, string $propertyPath = null): bool + public static function choice($value, array $choices, $message = null, ?string $propertyPath = null): bool { if (!\in_array($value, $choices, true)) { $message = \sprintf( @@ -1097,7 +1097,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function inArray($value, array $choices, $message = null, string $propertyPath = null): bool + public static function inArray($value, array $choices, $message = null, ?string $propertyPath = null): bool { return static::choice($value, $choices, $message, $propertyPath); } @@ -1115,7 +1115,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function numeric($value, $message = null, string $propertyPath = null): bool + public static function numeric($value, $message = null, ?string $propertyPath = null): bool { if (!\is_numeric($value)) { $message = \sprintf( @@ -1140,7 +1140,7 @@ class Assertion * * @return bool */ - public static function isResource($value, $message = null, string $propertyPath = null): bool + public static function isResource($value, $message = null, ?string $propertyPath = null): bool { if (!\is_resource($value)) { $message = \sprintf( @@ -1167,7 +1167,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isArray($value, $message = null, string $propertyPath = null): bool + public static function isArray($value, $message = null, ?string $propertyPath = null): bool { if (!\is_array($value)) { $message = \sprintf( @@ -1194,7 +1194,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isTraversable($value, $message = null, string $propertyPath = null): bool + public static function isTraversable($value, $message = null, ?string $propertyPath = null): bool { if (!\is_array($value) && !$value instanceof Traversable) { $message = \sprintf( @@ -1216,7 +1216,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isArrayAccessible($value, $message = null, string $propertyPath = null): bool + public static function isArrayAccessible($value, $message = null, ?string $propertyPath = null): bool { if (!\is_array($value) && !$value instanceof ArrayAccess) { $message = \sprintf( @@ -1233,7 +1233,7 @@ class Assertion /** * Assert that value is countable. * - * @param array|Countable|ResourceBundle|SimpleXMLElement $value + * @param mixed $value * @param string|callable|null $message * @param string|null $propertyPath * @@ -1243,7 +1243,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isCountable($value, $message = null, string $propertyPath = null): bool + public static function isCountable($value, $message = null, ?string $propertyPath = null): bool { if (\function_exists('is_countable')) { $assert = \is_countable($value); @@ -1272,7 +1272,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function keyExists($value, $key, $message = null, string $propertyPath = null): bool + public static function keyExists($value, $key, $message = null, ?string $propertyPath = null): bool { static::isArray($value, $message, $propertyPath); @@ -1297,7 +1297,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function keyNotExists($value, $key, $message = null, string $propertyPath = null): bool + public static function keyNotExists($value, $key, $message = null, ?string $propertyPath = null): bool { static::isArray($value, $message, $propertyPath); @@ -1321,7 +1321,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function uniqueValues(array $values, $message = null, string $propertyPath = null): bool + public static function uniqueValues(array $values, $message = null, ?string $propertyPath = null): bool { foreach ($values as $key => $value) { if (\array_search($value, $values, true) !== $key) { @@ -1346,7 +1346,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function keyIsset($value, $key, $message = null, string $propertyPath = null): bool + public static function keyIsset($value, $key, $message = null, ?string $propertyPath = null): bool { static::isArrayAccessible($value, $message, $propertyPath); @@ -1371,7 +1371,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notEmptyKey($value, $key, $message = null, string $propertyPath = null): bool + public static function notEmptyKey($value, $key, $message = null, ?string $propertyPath = null): bool { static::keyIsset($value, $key, $message, $propertyPath); static::notEmpty($value[$key], $message, $propertyPath); @@ -1387,7 +1387,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notBlank($value, $message = null, string $propertyPath = null): bool + public static function notBlank($value, $message = null, ?string $propertyPath = null): bool { if (false === $value || (empty($value) && '0' != $value) || (\is_string($value) && '' === \trim($value))) { $message = \sprintf( @@ -1417,7 +1417,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isInstanceOf($value, $className, $message = null, string $propertyPath = null): bool + public static function isInstanceOf($value, $className, $message = null, ?string $propertyPath = null): bool { if (!($value instanceof $className)) { $message = \sprintf( @@ -1448,7 +1448,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function notIsInstanceOf($value, $className, $message = null, string $propertyPath = null): bool + public static function notIsInstanceOf($value, $className, $message = null, ?string $propertyPath = null): bool { if ($value instanceof $className) { $message = \sprintf( @@ -1472,7 +1472,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function subclassOf($value, $className, $message = null, string $propertyPath = null): bool + public static function subclassOf($value, $className, $message = null, ?string $propertyPath = null): bool { if (!\is_subclass_of($value, $className)) { $message = \sprintf( @@ -1502,7 +1502,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function range($value, $minValue, $maxValue, $message = null, string $propertyPath = null): bool + public static function range($value, $minValue, $maxValue, $message = null, ?string $propertyPath = null): bool { static::numeric($value, $message, $propertyPath); @@ -1534,7 +1534,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function min($value, $minValue, $message = null, string $propertyPath = null): bool + public static function min($value, $minValue, $message = null, ?string $propertyPath = null): bool { static::numeric($value, $message, $propertyPath); @@ -1565,7 +1565,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function max($value, $maxValue, $message = null, string $propertyPath = null): bool + public static function max($value, $maxValue, $message = null, ?string $propertyPath = null): bool { static::numeric($value, $message, $propertyPath); @@ -1590,7 +1590,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function file($value, $message = null, string $propertyPath = null): bool + public static function file($value, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); static::notEmpty($value, $message, $propertyPath); @@ -1615,7 +1615,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function directory($value, $message = null, string $propertyPath = null): bool + public static function directory($value, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); @@ -1639,7 +1639,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function readable($value, $message = null, string $propertyPath = null): bool + public static function readable($value, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); @@ -1663,7 +1663,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function writeable($value, $message = null, string $propertyPath = null): bool + public static function writeable($value, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); @@ -1692,7 +1692,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function email($value, $message = null, string $propertyPath = null): bool + public static function email($value, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); @@ -1726,7 +1726,7 @@ class Assertion * @see https://github.com/symfony/Validator/blob/master/Constraints/UrlValidator.php * @see https://github.com/symfony/Validator/blob/master/Constraints/Url.php */ - public static function url($value, $message = null, string $propertyPath = null): bool + public static function url($value, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); @@ -1772,7 +1772,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function alnum($value, $message = null, string $propertyPath = null): bool + public static function alnum($value, $message = null, ?string $propertyPath = null): bool { try { static::regex($value, '(^([a-zA-Z]{1}[a-zA-Z0-9]*)$)', $message, $propertyPath); @@ -1801,7 +1801,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function true($value, $message = null, string $propertyPath = null): bool + public static function true($value, $message = null, ?string $propertyPath = null): bool { if (true !== $value) { $message = \sprintf( @@ -1828,7 +1828,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function false($value, $message = null, string $propertyPath = null): bool + public static function false($value, $message = null, ?string $propertyPath = null): bool { if (false !== $value) { $message = \sprintf( @@ -1855,7 +1855,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function classExists($value, $message = null, string $propertyPath = null): bool + public static function classExists($value, $message = null, ?string $propertyPath = null): bool { if (!\class_exists($value)) { $message = \sprintf( @@ -1882,7 +1882,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function interfaceExists($value, $message = null, string $propertyPath = null): bool + public static function interfaceExists($value, $message = null, ?string $propertyPath = null): bool { if (!\interface_exists($value)) { $message = \sprintf( @@ -1905,7 +1905,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function implementsInterface($class, $interfaceName, $message = null, string $propertyPath = null): bool + public static function implementsInterface($class, $interfaceName, $message = null, ?string $propertyPath = null): bool { try { $reflection = new ReflectionClass($class); @@ -1948,7 +1948,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isJsonString($value, $message = null, string $propertyPath = null): bool + public static function isJsonString($value, $message = null, ?string $propertyPath = null): bool { if (null === \json_decode($value) && JSON_ERROR_NONE !== \json_last_error()) { $message = \sprintf( @@ -1972,7 +1972,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function uuid($value, $message = null, string $propertyPath = null): bool + public static function uuid($value, $message = null, ?string $propertyPath = null): bool { $value = \str_replace(['urn:', 'uuid:', '{', '}'], '', $value); @@ -2002,7 +2002,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function e164($value, $message = null, string $propertyPath = null): bool + public static function e164($value, $message = null, ?string $propertyPath = null): bool { if (!\preg_match('/^\+?[1-9]\d{1,14}$/', $value)) { $message = \sprintf( @@ -2028,7 +2028,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function count($countable, $count, $message = null, string $propertyPath = null): bool + public static function count($countable, $count, $message = null, ?string $propertyPath = null): bool { if ($count !== \count($countable)) { $message = \sprintf( @@ -2052,7 +2052,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function minCount($countable, $count, $message = null, string $propertyPath = null): bool + public static function minCount($countable, $count, $message = null, ?string $propertyPath = null): bool { if ($count > \count($countable)) { $message = \sprintf( @@ -2076,7 +2076,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function maxCount($countable, $count, $message = null, string $propertyPath = null): bool + public static function maxCount($countable, $count, $message = null, ?string $propertyPath = null): bool { if ($count < \count($countable)) { $message = \sprintf( @@ -2147,7 +2147,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function choicesNotEmpty(array $values, array $choices, $message = null, string $propertyPath = null): bool + public static function choicesNotEmpty(array $values, array $choices, $message = null, ?string $propertyPath = null): bool { static::notEmpty($values, $message, $propertyPath); @@ -2167,7 +2167,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function methodExists($value, $object, $message = null, string $propertyPath = null): bool + public static function methodExists($value, $object, $message = null, ?string $propertyPath = null): bool { static::isObject($object, $message, $propertyPath); @@ -2196,7 +2196,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isObject($value, $message = null, string $propertyPath = null): bool + public static function isObject($value, $message = null, ?string $propertyPath = null): bool { if (!\is_object($value)) { $message = \sprintf( @@ -2219,7 +2219,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function lessThan($value, $limit, $message = null, string $propertyPath = null): bool + public static function lessThan($value, $limit, $message = null, ?string $propertyPath = null): bool { if ($value >= $limit) { $message = \sprintf( @@ -2243,7 +2243,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function lessOrEqualThan($value, $limit, $message = null, string $propertyPath = null): bool + public static function lessOrEqualThan($value, $limit, $message = null, ?string $propertyPath = null): bool { if ($value > $limit) { $message = \sprintf( @@ -2267,7 +2267,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function greaterThan($value, $limit, $message = null, string $propertyPath = null): bool + public static function greaterThan($value, $limit, $message = null, ?string $propertyPath = null): bool { if ($value <= $limit) { $message = \sprintf( @@ -2291,7 +2291,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function greaterOrEqualThan($value, $limit, $message = null, string $propertyPath = null): bool + public static function greaterOrEqualThan($value, $limit, $message = null, ?string $propertyPath = null): bool { if ($value < $limit) { $message = \sprintf( @@ -2317,7 +2317,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function between($value, $lowerLimit, $upperLimit, $message = null, string $propertyPath = null): bool + public static function between($value, $lowerLimit, $upperLimit, $message = null, ?string $propertyPath = null): bool { if ($lowerLimit > $value || $value > $upperLimit) { $message = \sprintf( @@ -2344,7 +2344,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function betweenExclusive($value, $lowerLimit, $upperLimit, $message = null, string $propertyPath = null): bool + public static function betweenExclusive($value, $lowerLimit, $upperLimit, $message = null, ?string $propertyPath = null): bool { if ($lowerLimit >= $value || $value >= $upperLimit) { $message = \sprintf( @@ -2368,7 +2368,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function extensionLoaded($value, $message = null, string $propertyPath = null): bool + public static function extensionLoaded($value, $message = null, ?string $propertyPath = null): bool { if (!\extension_loaded($value)) { $message = \sprintf( @@ -2394,7 +2394,7 @@ class Assertion * * @see http://php.net/manual/function.date.php#refsect1-function.date-parameters */ - public static function date($value, $format, $message = null, string $propertyPath = null): bool + public static function date($value, $format, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); static::string($format, $message, $propertyPath); @@ -2422,7 +2422,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function objectOrClass($value, $message = null, string $propertyPath = null): bool + public static function objectOrClass($value, $message = null, ?string $propertyPath = null): bool { if (!\is_object($value)) { static::classExists($value, $message, $propertyPath); @@ -2440,7 +2440,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function propertyExists($value, $property, $message = null, string $propertyPath = null): bool + public static function propertyExists($value, $property, $message = null, ?string $propertyPath = null): bool { static::objectOrClass($value); @@ -2465,7 +2465,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function propertiesExist($value, array $properties, $message = null, string $propertyPath = null): bool + public static function propertiesExist($value, array $properties, $message = null, ?string $propertyPath = null): bool { static::objectOrClass($value); static::allString($properties, $message, $propertyPath); @@ -2500,7 +2500,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function version($version1, $operator, $version2, $message = null, string $propertyPath = null): bool + public static function version($version1, $operator, $version2, $message = null, ?string $propertyPath = null): bool { static::notEmpty($operator, 'versionCompare operator is required and cannot be empty.'); @@ -2527,7 +2527,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function phpVersion($operator, $version, $message = null, string $propertyPath = null): bool + public static function phpVersion($operator, $version, $message = null, ?string $propertyPath = null): bool { static::defined('PHP_VERSION'); @@ -2544,7 +2544,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function extensionVersion($extension, $operator, $version, $message = null, string $propertyPath = null): bool + public static function extensionVersion($extension, $operator, $version, $message = null, ?string $propertyPath = null): bool { static::extensionLoaded($extension, $message, $propertyPath); @@ -2564,7 +2564,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function isCallable($value, $message = null, string $propertyPath = null): bool + public static function isCallable($value, $message = null, ?string $propertyPath = null): bool { if (!\is_callable($value)) { $message = \sprintf( @@ -2589,7 +2589,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function satisfy($value, $callback, $message = null, string $propertyPath = null): bool + public static function satisfy($value, $callback, $message = null, ?string $propertyPath = null): bool { static::isCallable($callback); @@ -2617,7 +2617,7 @@ class Assertion * * @see http://php.net/manual/filter.filters.flags.php */ - public static function ip($value, $flag = null, $message = null, string $propertyPath = null): bool + public static function ip($value, $flag = null, $message = null, ?string $propertyPath = null): bool { static::string($value, $message, $propertyPath); if ($flag === null) { @@ -2648,7 +2648,7 @@ class Assertion * * @see http://php.net/manual/filter.filters.flags.php */ - public static function ipv4($value, $flag = null, $message = null, string $propertyPath = null): bool + public static function ipv4($value, $flag = null, $message = null, ?string $propertyPath = null): bool { static::ip($value, $flag | FILTER_FLAG_IPV4, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv4 address.'), $propertyPath); @@ -2667,7 +2667,7 @@ class Assertion * * @see http://php.net/manual/filter.filters.flags.php */ - public static function ipv6($value, $flag = null, $message = null, string $propertyPath = null): bool + public static function ipv6($value, $flag = null, $message = null, ?string $propertyPath = null): bool { static::ip($value, $flag | FILTER_FLAG_IPV6, static::generateMessage($message ?: 'Value "%s" was expected to be a valid IPv6 address.'), $propertyPath); @@ -2680,7 +2680,7 @@ class Assertion * @param mixed $constant * @param string|callable|null $message */ - public static function defined($constant, $message = null, string $propertyPath = null): bool + public static function defined($constant, $message = null, ?string $propertyPath = null): bool { if (!\defined($constant)) { $message = \sprintf(static::generateMessage($message ?: 'Value "%s" expected to be a defined constant.'), $constant); @@ -2699,7 +2699,7 @@ class Assertion * * @throws AssertionFailedException */ - public static function base64($value, $message = null, string $propertyPath = null): bool + public static function base64($value, $message = null, ?string $propertyPath = null): bool { if (false === \base64_decode($value, true)) { $message = \sprintf(static::generateMessage($message ?: 'Value "%s" is not a valid base64 string.'), $value); diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/AssertionChain.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/AssertionChain.php index 4c444350..8d1f1b3c 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/AssertionChain.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/AssertionChain.php @@ -151,7 +151,7 @@ class AssertionChain * @param mixed $value * @param string|callable|null $defaultMessage */ - public function __construct($value, $defaultMessage = null, string $defaultPropertyPath = null) + public function __construct($value, $defaultMessage = null, ?string $defaultPropertyPath = null) { $this->value = $value; $this->defaultMessage = $defaultMessage; diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/InvalidArgumentException.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/InvalidArgumentException.php index 9516e077..54f85aad 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/InvalidArgumentException.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/InvalidArgumentException.php @@ -31,7 +31,7 @@ class InvalidArgumentException extends \InvalidArgumentException implements Asse */ private $constraints; - public function __construct($message, $code, string $propertyPath = null, $value = null, array $constraints = []) + public function __construct($message, $code, ?string $propertyPath = null, $value = null, array $constraints = []) { parent::__construct($message, $code); diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/LazyAssertion.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/LazyAssertion.php index b3052178..f7b6cd71 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/LazyAssertion.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/LazyAssertion.php @@ -133,7 +133,7 @@ class LazyAssertion * * @return static */ - public function that($value, string $propertyPath = null, $defaultMessage = null) + public function that($value, ?string $propertyPath = null, $defaultMessage = null) { $this->currentChainFailed = false; $this->thisChainTryAll = false; diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/functions.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/functions.php index 1a4e84d9..77cdcedd 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/functions.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/beberlei/assert/lib/Assert/functions.php @@ -32,7 +32,7 @@ namespace Assert; * The assertion chain can be stateful, that means be careful when you reuse * it. You should never pass around the chain. */ -function that($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain +function that($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain { return Assert::that($value, $defaultMessage, $defaultPropertyPath); } @@ -44,7 +44,7 @@ function that($value, $defaultMessage = null, string $defaultPropertyPath = null * @param string|callable|null $defaultMessage * @param string $defaultPropertyPath */ -function thatAll($values, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain +function thatAll($values, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain { return Assert::thatAll($values, $defaultMessage, $defaultPropertyPath); } @@ -58,7 +58,7 @@ function thatAll($values, $defaultMessage = null, string $defaultPropertyPath = * * @deprecated In favour of Assert::thatNullOr($value, $defaultMessage = null, $defaultPropertyPath = null) */ -function thatNullOr($value, $defaultMessage = null, string $defaultPropertyPath = null): AssertionChain +function thatNullOr($value, $defaultMessage = null, ?string $defaultPropertyPath = null): AssertionChain { return Assert::thatNullOr($value, $defaultMessage, $defaultPropertyPath); } diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/InstalledVersions.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/InstalledVersions.php index 51e734a7..2052022f 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/InstalledVersions.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/InstalledVersions.php @@ -26,12 +26,23 @@ use Composer\Semver\VersionParser; */ class InstalledVersions { + /** + * @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to + * @internal + */ + private static $selfDir = null; + /** * @var mixed[]|null * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; + /** + * @var bool + */ + private static $installedIsLocalDir; + /** * @var bool|null */ @@ -309,6 +320,24 @@ class InstalledVersions { self::$installed = $data; self::$installedByVendor = array(); + + // when using reload, we disable the duplicate protection to ensure that self::$installed data is + // always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not, + // so we have to assume it does not, and that may result in duplicate data being returned when listing + // all installed packages for example + self::$installedIsLocalDir = false; + } + + /** + * @return string + */ + private static function getSelfDir() + { + if (self::$selfDir === null) { + self::$selfDir = strtr(__DIR__, '\\', '/'); + } + + return self::$selfDir; } /** @@ -322,19 +351,27 @@ class InstalledVersions } $installed = array(); + $copiedLocalDir = false; if (self::$canGetVendors) { + $selfDir = self::getSelfDir(); foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) { + $vendorDir = strtr($vendorDir, '\\', '/'); if (isset(self::$installedByVendor[$vendorDir])) { $installed[] = self::$installedByVendor[$vendorDir]; } elseif (is_file($vendorDir.'/composer/installed.php')) { /** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $required */ $required = require $vendorDir.'/composer/installed.php'; - $installed[] = self::$installedByVendor[$vendorDir] = $required; - if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) { - self::$installed = $installed[count($installed) - 1]; + self::$installedByVendor[$vendorDir] = $required; + $installed[] = $required; + if (self::$installed === null && $vendorDir.'/composer' === $selfDir) { + self::$installed = $required; + self::$installedIsLocalDir = true; } } + if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) { + $copiedLocalDir = true; + } } } @@ -350,7 +387,7 @@ class InstalledVersions } } - if (self::$installed !== array()) { + if (self::$installed !== array() && !$copiedLocalDir) { $installed[] = self::$installed; } diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_psr4.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_psr4.php index ce2067bb..aebc1b12 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_psr4.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_psr4.php @@ -16,12 +16,12 @@ return array( 'Ramsey\\Uuid\\' => array($vendorDir . '/ramsey/uuid/src'), 'Ramsey\\Collection\\' => array($vendorDir . '/ramsey/collection/src'), 'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'), - 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'), + 'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'), 'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'), 'Nyholm\\Psr7\\' => array($vendorDir . '/nyholm/psr7/src'), 'Nyholm\\Psr7Server\\' => array($vendorDir . '/nyholm/psr7-server/src'), - 'League\\Uri\\' => array($vendorDir . '/league/uri-interfaces/src', $vendorDir . '/league/uri/src'), - 'Jose\\Component\\Signature\\Algorithm\\' => array($vendorDir . '/web-token/jwt-signature-algorithm-ecdsa', $vendorDir . '/web-token/jwt-signature-algorithm-eddsa', $vendorDir . '/web-token/jwt-signature-algorithm-rsa'), + 'League\\Uri\\' => array($vendorDir . '/league/uri/src', $vendorDir . '/league/uri-interfaces/src'), + 'Jose\\Component\\Signature\\Algorithm\\' => array($vendorDir . '/web-token/jwt-signature-algorithm-rsa', $vendorDir . '/web-token/jwt-signature-algorithm-eddsa', $vendorDir . '/web-token/jwt-signature-algorithm-ecdsa'), 'Jose\\Component\\Signature\\' => array($vendorDir . '/web-token/jwt-signature'), 'Jose\\Component\\KeyManagement\\' => array($vendorDir . '/web-token/jwt-key-mgmt'), 'Jose\\Component\\Core\\' => array($vendorDir . '/web-token/jwt-core'), diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_static.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_static.php index 618b51a1..bdf29da2 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_static.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/autoload_static.php @@ -104,12 +104,12 @@ class ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef ); public static $prefixLengthsPsr4 = array ( - 'W' => + 'W' => array ( 'Webauthn\\MetadataService\\' => 25, 'Webauthn\\' => 9, ), - 'S' => + 'S' => array ( 'Symfony\\Polyfill\\Php81\\' => 23, 'Symfony\\Polyfill\\Php80\\' => 23, @@ -117,157 +117,157 @@ class ComposerStaticInite99fdfd0dbb5e609b534e430fe6b54ef 'Symfony\\Component\\Process\\' => 26, 'Safe\\' => 5, ), - 'R' => + 'R' => array ( 'Ramsey\\Uuid\\' => 12, 'Ramsey\\Collection\\' => 18, ), - 'P' => + 'P' => array ( 'Psr\\Log\\' => 8, 'Psr\\Http\\Message\\' => 17, 'Psr\\Http\\Client\\' => 16, ), - 'N' => + 'N' => array ( 'Nyholm\\Psr7\\' => 12, 'Nyholm\\Psr7Server\\' => 18, ), - 'L' => + 'L' => array ( 'League\\Uri\\' => 11, ), - 'J' => + 'J' => array ( 'Jose\\Component\\Signature\\Algorithm\\' => 35, 'Jose\\Component\\Signature\\' => 25, 'Jose\\Component\\KeyManagement\\' => 29, 'Jose\\Component\\Core\\' => 20, ), - 'F' => + 'F' => array ( 'FG\\' => 3, ), - 'C' => + 'C' => array ( 'Cose\\' => 5, 'CBOR\\' => 5, ), - 'B' => + 'B' => array ( 'Brick\\Math\\' => 11, 'Base64Url\\' => 10, ), - 'A' => + 'A' => array ( 'Assert\\' => 7, ), ); public static $prefixDirsPsr4 = array ( - 'Webauthn\\MetadataService\\' => + 'Webauthn\\MetadataService\\' => array ( 0 => __DIR__ . '/..' . '/web-auth/metadata-service/src', ), - 'Webauthn\\' => + 'Webauthn\\' => array ( 0 => __DIR__ . '/..' . '/web-auth/webauthn-lib/src', ), - 'Symfony\\Polyfill\\Php81\\' => + 'Symfony\\Polyfill\\Php81\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php81', ), - 'Symfony\\Polyfill\\Php80\\' => + 'Symfony\\Polyfill\\Php80\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-php80', ), - 'Symfony\\Polyfill\\Ctype\\' => + 'Symfony\\Polyfill\\Ctype\\' => array ( 0 => __DIR__ . '/..' . '/symfony/polyfill-ctype', ), - 'Symfony\\Component\\Process\\' => + 'Symfony\\Component\\Process\\' => array ( 0 => __DIR__ . '/..' . '/symfony/process', ), - 'Safe\\' => + 'Safe\\' => array ( 0 => __DIR__ . '/..' . '/thecodingmachine/safe/lib', 1 => __DIR__ . '/..' . '/thecodingmachine/safe/deprecated', 2 => __DIR__ . '/..' . '/thecodingmachine/safe/generated', ), - 'Ramsey\\Uuid\\' => + 'Ramsey\\Uuid\\' => array ( 0 => __DIR__ . '/..' . '/ramsey/uuid/src', ), - 'Ramsey\\Collection\\' => + 'Ramsey\\Collection\\' => array ( 0 => __DIR__ . '/..' . '/ramsey/collection/src', ), - 'Psr\\Log\\' => + 'Psr\\Log\\' => array ( 0 => __DIR__ . '/..' . '/psr/log/Psr/Log', ), - 'Psr\\Http\\Message\\' => + 'Psr\\Http\\Message\\' => array ( - 0 => __DIR__ . '/..' . '/psr/http-message/src', - 1 => __DIR__ . '/..' . '/psr/http-factory/src', + 0 => __DIR__ . '/..' . '/psr/http-factory/src', + 1 => __DIR__ . '/..' . '/psr/http-message/src', ), - 'Psr\\Http\\Client\\' => + 'Psr\\Http\\Client\\' => array ( 0 => __DIR__ . '/..' . '/psr/http-client/src', ), - 'Nyholm\\Psr7\\' => + 'Nyholm\\Psr7\\' => array ( 0 => __DIR__ . '/..' . '/nyholm/psr7/src', ), - 'Nyholm\\Psr7Server\\' => + 'Nyholm\\Psr7Server\\' => array ( 0 => __DIR__ . '/..' . '/nyholm/psr7-server/src', ), - 'League\\Uri\\' => + 'League\\Uri\\' => array ( - 0 => __DIR__ . '/..' . '/league/uri-interfaces/src', - 1 => __DIR__ . '/..' . '/league/uri/src', + 0 => __DIR__ . '/..' . '/league/uri/src', + 1 => __DIR__ . '/..' . '/league/uri-interfaces/src', ), - 'Jose\\Component\\Signature\\Algorithm\\' => + 'Jose\\Component\\Signature\\Algorithm\\' => array ( - 0 => __DIR__ . '/..' . '/web-token/jwt-signature-algorithm-ecdsa', + 0 => __DIR__ . '/..' . '/web-token/jwt-signature-algorithm-rsa', 1 => __DIR__ . '/..' . '/web-token/jwt-signature-algorithm-eddsa', - 2 => __DIR__ . '/..' . '/web-token/jwt-signature-algorithm-rsa', + 2 => __DIR__ . '/..' . '/web-token/jwt-signature-algorithm-ecdsa', ), - 'Jose\\Component\\Signature\\' => + 'Jose\\Component\\Signature\\' => array ( 0 => __DIR__ . '/..' . '/web-token/jwt-signature', ), - 'Jose\\Component\\KeyManagement\\' => + 'Jose\\Component\\KeyManagement\\' => array ( 0 => __DIR__ . '/..' . '/web-token/jwt-key-mgmt', ), - 'Jose\\Component\\Core\\' => + 'Jose\\Component\\Core\\' => array ( 0 => __DIR__ . '/..' . '/web-token/jwt-core', ), - 'FG\\' => + 'FG\\' => array ( 0 => __DIR__ . '/..' . '/fgrosse/phpasn1/lib', ), - 'Cose\\' => + 'Cose\\' => array ( 0 => __DIR__ . '/..' . '/web-auth/cose-lib/src', ), - 'CBOR\\' => + 'CBOR\\' => array ( 0 => __DIR__ . '/..' . '/spomky-labs/cbor-php/src', ), - 'Brick\\Math\\' => + 'Brick\\Math\\' => array ( 0 => __DIR__ . '/..' . '/brick/math/src', ), - 'Base64Url\\' => + 'Base64Url\\' => array ( 0 => __DIR__ . '/..' . '/spomky-labs/base64url/src', ), - 'Assert\\' => + 'Assert\\' => array ( 0 => __DIR__ . '/..' . '/beberlei/assert/lib/Assert', ), diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.json b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.json index 9a36beca..8bfeee31 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.json +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.json @@ -2,17 +2,17 @@ "packages": [ { "name": "beberlei/assert", - "version": "v3.3.2", - "version_normalized": "3.3.2.0", + "version": "v3.3.3", + "version_normalized": "3.3.3.0", "source": { "type": "git", "url": "https://github.com/beberlei/assert.git", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655" + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/beberlei/assert/zipball/cb70015c04be1baee6f5f5c953703347c0ac1655", - "reference": "cb70015c04be1baee6f5f5c953703347c0ac1655", + "url": "https://api.github.com/repos/beberlei/assert/zipball/b5fd8eacd8915a1b627b8bfc027803f1939734dd", + "reference": "b5fd8eacd8915a1b627b8bfc027803f1939734dd", "shasum": "", "mirrors": [ { @@ -26,7 +26,7 @@ "ext-json": "*", "ext-mbstring": "*", "ext-simplexml": "*", - "php": "^7.0 || ^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", @@ -37,7 +37,7 @@ "suggest": { "ext-intl": "Needed to allow Assertion::count(), Assertion::isCountable(), Assertion::minCount(), and Assertion::maxCount() to operate on ResourceBundles" }, - "time": "2021-12-16T21:41:27+00:00", + "time": "2024-07-15T13:18:35+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -72,7 +72,7 @@ ], "support": { "issues": "https://github.com/beberlei/assert/issues", - "source": "https://github.com/beberlei/assert/tree/v3.3.2" + "source": "https://github.com/beberlei/assert/tree/v3.3.3" }, "install-path": "../beberlei/assert" }, @@ -991,11 +991,11 @@ "time": "2021-09-25T23:10:38+00:00", "type": "library", "extra": { - "branch-alias": { - "dev-main": "4.x-dev" - }, "captainhook": { "force-install": true + }, + "branch-alias": { + "dev-main": "4.x-dev" } }, "installation-source": "dist", @@ -1198,8 +1198,8 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.31.0", - "version_normalized": "1.31.0.0", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", @@ -1230,8 +1230,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1266,7 +1266,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.32.0" }, "funding": [ { @@ -1286,17 +1286,17 @@ }, { "name": "symfony/polyfill-php80", - "version": "v1.31.0", - "version_normalized": "1.31.0.0", + "version": "v1.33.0", + "version_normalized": "1.33.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8" + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", - "reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/0cc9dd0f17f61d8131e7df6b84bd344899fe2608", + "reference": "0cc9dd0f17f61d8131e7df6b84bd344899fe2608", "shasum": "", "mirrors": [ { @@ -1308,12 +1308,12 @@ "require": { "php": ">=7.2" }, - "time": "2024-09-09T11:45:10+00:00", + "time": "2025-01-02T08:10:11+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1355,7 +1355,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.33.0" }, "funding": [ { @@ -1366,6 +1366,10 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" @@ -1375,17 +1379,17 @@ }, { "name": "symfony/polyfill-php81", - "version": "v1.30.0", - "version_normalized": "1.30.0.0", + "version": "v1.32.0", + "version_normalized": "1.32.0.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php81.git", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", - "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", + "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c", "shasum": "", "mirrors": [ { @@ -1395,14 +1399,14 @@ ] }, "require": { - "php": ">=7.1" + "php": ">=7.2" }, - "time": "2024-06-19T12:30:46+00:00", + "time": "2024-09-09T11:45:10+00:00", "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "installation-source": "dist", @@ -1440,7 +1444,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php81/tree/v1.30.0" + "source": "https://github.com/symfony/polyfill-php81/tree/v1.32.0" }, "funding": [ { @@ -1460,17 +1464,17 @@ }, { "name": "symfony/process", - "version": "v5.4.40", - "version_normalized": "5.4.40.0", + "version": "v5.4.47", + "version_normalized": "5.4.47.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046" + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/deedcb3bb4669cae2148bc920eafd2b16dc7c046", - "reference": "deedcb3bb4669cae2148bc920eafd2b16dc7c046", + "url": "https://api.github.com/repos/symfony/process/zipball/5d1662fb32ebc94f17ddb8d635454a776066733d", + "reference": "5d1662fb32ebc94f17ddb8d635454a776066733d", "shasum": "", "mirrors": [ { @@ -1483,7 +1487,7 @@ "php": ">=7.2.5", "symfony/polyfill-php80": "^1.16" }, - "time": "2024-05-31T14:33:22+00:00", + "time": "2024-11-06T11:36:42+00:00", "type": "library", "installation-source": "dist", "autoload": { @@ -1511,7 +1515,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.40" + "source": "https://github.com/symfony/process/tree/v5.4.47" }, "funding": [ { diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.php index f66df5e4..0f770739 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/composer/installed.php @@ -1,9 +1,9 @@ array( 'name' => '__root__', - 'pretty_version' => '1.3.4', - 'version' => '1.3.4.0', - 'reference' => '88207a7a7032f209d572a80f06699769886cdeb3', + 'pretty_version' => '1.4.1', + 'version' => '1.4.1.0', + 'reference' => '01964b4933ae48870823ca1620868423cfd4bf2b', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -11,18 +11,18 @@ ), 'versions' => array( '__root__' => array( - 'pretty_version' => '1.3.4', - 'version' => '1.3.4.0', - 'reference' => '88207a7a7032f209d572a80f06699769886cdeb3', + 'pretty_version' => '1.4.1', + 'version' => '1.4.1.0', + 'reference' => '01964b4933ae48870823ca1620868423cfd4bf2b', 'type' => 'library', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), 'dev_requirement' => false, ), 'beberlei/assert' => array( - 'pretty_version' => 'v3.3.2', - 'version' => '3.3.2.0', - 'reference' => 'cb70015c04be1baee6f5f5c953703347c0ac1655', + 'pretty_version' => 'v3.3.3', + 'version' => '3.3.3.0', + 'reference' => 'b5fd8eacd8915a1b627b8bfc027803f1939734dd', 'type' => 'library', 'install_path' => __DIR__ . '/../beberlei/assert', 'aliases' => array(), @@ -179,8 +179,8 @@ 'dev_requirement' => false, ), 'symfony/polyfill-ctype' => array( - 'pretty_version' => 'v1.31.0', - 'version' => '1.31.0.0', + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', 'reference' => 'a3cc8b044a6ea513310cbd48ef7333b384945638', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-ctype', @@ -188,27 +188,27 @@ 'dev_requirement' => false, ), 'symfony/polyfill-php80' => array( - 'pretty_version' => 'v1.31.0', - 'version' => '1.31.0.0', - 'reference' => '60328e362d4c2c802a54fcbf04f9d3fb892b4cf8', + 'pretty_version' => 'v1.33.0', + 'version' => '1.33.0.0', + 'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php80', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/polyfill-php81' => array( - 'pretty_version' => 'v1.30.0', - 'version' => '1.30.0.0', - 'reference' => '3fb075789fb91f9ad9af537c4012d523085bd5af', + 'pretty_version' => 'v1.32.0', + 'version' => '1.32.0.0', + 'reference' => '4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/polyfill-php81', 'aliases' => array(), 'dev_requirement' => false, ), 'symfony/process' => array( - 'pretty_version' => 'v5.4.40', - 'version' => '5.4.40.0', - 'reference' => 'deedcb3bb4669cae2148bc920eafd2b16dc7c046', + 'pretty_version' => 'v5.4.47', + 'version' => '5.4.47.0', + 'reference' => '5d1662fb32ebc94f17ddb8d635454a776066733d', 'type' => 'library', 'install_path' => __DIR__ . '/../symfony/process', 'aliases' => array(), diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php80/PhpToken.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php80/PhpToken.php index fe6e6910..cd78c4cc 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php80/PhpToken.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php80/PhpToken.php @@ -29,7 +29,7 @@ class PhpToken implements \Stringable public $text; /** - * @var int + * @var -1|positive-int */ public $line; @@ -38,6 +38,9 @@ class PhpToken implements \Stringable */ public $pos; + /** + * @param -1|positive-int $line + */ public function __construct(int $id, string $text, int $line = -1, int $position = -1) { $this->id = $id; @@ -80,7 +83,7 @@ class PhpToken implements \Stringable } /** - * @return static[] + * @return list */ public static function tokenize(string $code, int $flags = 0): array { diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php81/composer.json b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php81/composer.json index 381af79a..28b6408e 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php81/composer.json +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/polyfill-php81/composer.json @@ -16,7 +16,7 @@ } ], "require": { - "php": ">=7.1" + "php": ">=7.2" }, "autoload": { "psr-4": { "Symfony\\Polyfill\\Php81\\": "" }, diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/ExecutableFinder.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/ExecutableFinder.php index f392c962..89edd22f 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/ExecutableFinder.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/ExecutableFinder.php @@ -19,7 +19,15 @@ namespace Symfony\Component\Process; */ class ExecutableFinder { - private $suffixes = ['.exe', '.bat', '.cmd', '.com']; + private const CMD_BUILTINS = [ + 'assoc', 'break', 'call', 'cd', 'chdir', 'cls', 'color', 'copy', 'date', + 'del', 'dir', 'echo', 'endlocal', 'erase', 'exit', 'for', 'ftype', 'goto', + 'help', 'if', 'label', 'md', 'mkdir', 'mklink', 'move', 'path', 'pause', + 'popd', 'prompt', 'pushd', 'rd', 'rem', 'ren', 'rename', 'rmdir', 'set', + 'setlocal', 'shift', 'start', 'time', 'title', 'type', 'ver', 'vol', + ]; + + private $suffixes = []; /** * Replaces default suffixes of executable. @@ -48,39 +56,48 @@ class ExecutableFinder */ public function find(string $name, ?string $default = null, array $extraDirs = []) { - if (\ini_get('open_basedir')) { - $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs); - $dirs = []; - foreach ($searchPath as $path) { - // Silencing against https://bugs.php.net/69240 - if (@is_dir($path)) { - $dirs[] = $path; - } else { - if (basename($path) == $name && @is_executable($path)) { - return $path; - } - } - } - } else { - $dirs = array_merge( - explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), - $extraDirs - ); + // windows built-in commands that are present in cmd.exe should not be resolved using PATH as they do not exist as exes + if ('\\' === \DIRECTORY_SEPARATOR && \in_array(strtolower($name), self::CMD_BUILTINS, true)) { + return $name; } - $suffixes = ['']; + $dirs = array_merge( + explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')), + $extraDirs + ); + + $suffixes = []; if ('\\' === \DIRECTORY_SEPARATOR) { $pathExt = getenv('PATHEXT'); - $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes); + $suffixes = $this->suffixes; + $suffixes = array_merge($suffixes, $pathExt ? explode(\PATH_SEPARATOR, $pathExt) : ['.exe', '.bat', '.cmd', '.com']); } + $suffixes = '' !== pathinfo($name, PATHINFO_EXTENSION) ? array_merge([''], $suffixes) : array_merge($suffixes, ['']); foreach ($suffixes as $suffix) { foreach ($dirs as $dir) { + if ('' === $dir) { + $dir = '.'; + } if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) { return $file; } + + if (!@is_dir($dir) && basename($dir) === $name.$suffix && @is_executable($dir)) { + return $dir; + } } } + if ('\\' === \DIRECTORY_SEPARATOR || !\function_exists('exec') || \strlen($name) !== strcspn($name, '/'.\DIRECTORY_SEPARATOR)) { + return $default; + } + + $execResult = exec('command -v -- '.escapeshellarg($name)); + + if (($executablePath = substr($execResult, 0, strpos($execResult, \PHP_EOL) ?: null)) && @is_executable($executablePath)) { + return $executablePath; + } + return $default; } } diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/PhpExecutableFinder.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/PhpExecutableFinder.php index 45dbcca4..c3a9680d 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/PhpExecutableFinder.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/PhpExecutableFinder.php @@ -34,15 +34,8 @@ class PhpExecutableFinder public function find(bool $includeArgs = true) { if ($php = getenv('PHP_BINARY')) { - if (!is_executable($php)) { - $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v --'; - if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) { - if (!is_executable($php)) { - return false; - } - } else { - return false; - } + if (!is_executable($php) && !$php = $this->executableFinder->find($php)) { + return false; } if (@is_dir($php)) { diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/Process.php b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/Process.php index a4b0a784..91f9e8fe 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/Process.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn-vendor/symfony/process/Process.php @@ -352,7 +352,7 @@ class Process implements \IteratorAggregate $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options); - if (!\is_resource($this->process)) { + if (!$this->process) { throw new RuntimeException('Unable to launch a new process.'); } $this->status = self::STATUS_STARTED; @@ -1456,8 +1456,9 @@ class Process implements \IteratorAggregate private function close(): int { $this->processPipes->close(); - if (\is_resource($this->process)) { + if ($this->process) { proc_close($this->process); + $this->process = null; } $this->exitcode = $this->processInformation['exitcode']; $this->status = self::STATUS_TERMINATED; @@ -1591,7 +1592,14 @@ class Process implements \IteratorAggregate $cmd ); - $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; + static $comSpec; + + if (!$comSpec && $comSpec = (new ExecutableFinder())->find('cmd.exe')) { + // Escape according to CommandLineToArgvW rules + $comSpec = '"'.preg_replace('{(\\\\*+)"}', '$1$1\"', $comSpec) .'"'; + } + + $cmd = ($comSpec ?? 'cmd').' /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')'; foreach ($this->processPipes->getFiles() as $offset => $filename) { $cmd .= ' '.$offset.'>"'.$filename.'"'; } @@ -1637,7 +1645,7 @@ class Process implements \IteratorAggregate if (str_contains($argument, "\0")) { $argument = str_replace("\0", '?', $argument); } - if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) { + if (!preg_match('/[()%!^"<>&|\s]/', $argument)) { return $argument; } $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument); diff --git a/wp-content/plugins/wp-webauthn/wp-webauthn.php b/wp-content/plugins/wp-webauthn/wp-webauthn.php index ae962256..5c554b06 100644 --- a/wp-content/plugins/wp-webauthn/wp-webauthn.php +++ b/wp-content/plugins/wp-webauthn/wp-webauthn.php @@ -3,12 +3,13 @@ Plugin Name: WP-WebAuthn Plugin URI: https://flyhigher.top Description: WP-WebAuthn allows you to safely login to your WordPress site without password. -Version: 1.3.4 +Version: 1.4.1 Author: Axton Author URI: https://axton.cc License: GPLv3 Text Domain: wp-webauthn Domain Path: /languages +Network: true */ /* Copyright 2020 Axton This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version of the License, or (at your option) any later version. @@ -17,53 +18,284 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ +if (!defined('ABSPATH')) { + exit; +} + +function wwa_register_table() { + global $wpdb; + $wpdb->wwa_credentials = $wpdb->base_prefix . 'wwa_credentials'; +} +wwa_register_table(); +add_action('plugins_loaded', 'wwa_register_table', 0); + +function wwa_create_table() { + global $wpdb; + $table = $wpdb->wwa_credentials; + $charset_collate = $wpdb->get_charset_collate(); + + $sql = "CREATE TABLE $table ( + credential_id varchar(512) NOT NULL, + user_id bigint(20) unsigned NOT NULL, + registered_blog_id bigint(20) unsigned NOT NULL DEFAULT 1, + credential_source longtext NOT NULL, + user_handle varchar(255) NOT NULL, + human_name text NOT NULL, + authenticator_type varchar(50) NOT NULL, + usernameless tinyint(1) NOT NULL DEFAULT 0, + added datetime NOT NULL, + last_used varchar(50) NOT NULL DEFAULT '-', + PRIMARY KEY (credential_id), + KEY idx_user_id (user_id), + KEY idx_user_handle (user_handle), + KEY idx_blog_user (registered_blog_id, user_id) + ) $charset_collate;"; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta($sql); +} + +function wwa_migrate_credentials_for_site(){ + if(get_option('wwa_credentials_migrated')){ + return; + } + + global $wpdb; + $options = get_option('wwa_options'); + if(!is_array($options)){ + update_option('wwa_credentials_migrated', true); + return; + } + + $user_id_map = isset($options['user_id']) && is_array($options['user_id']) ? $options['user_id'] : array(); + $raw_creds = isset($options['user_credentials']) ? $options['user_credentials'] : '{}'; + $raw_meta = isset($options['user_credentials_meta']) ? $options['user_credentials_meta'] : '{}'; + + $credentials = json_decode($raw_creds, true); + $credentials_meta = json_decode($raw_meta, true); + + if(!is_array($credentials) || !is_array($credentials_meta)){ + update_option('wwa_credentials_migrated', true); + return; + } + + foreach($user_id_map as $user_login => $user_handle){ + $wp_user = get_user_by('login', $user_login); + if($wp_user === false){ + continue; + } + $existing = get_user_meta($wp_user->ID, 'wwa_user_handle', true); + if(!$existing){ + update_user_meta($wp_user->ID, 'wwa_user_handle', $user_handle); + } + } + + $handle_to_login = array(); + foreach($user_id_map as $user_login => $user_handle){ + $handle_to_login[$user_handle] = $user_login; + } + + $blog_id = get_current_blog_id(); + foreach($credentials_meta as $cred_id => $meta){ + if(!isset($meta['user']) || !isset($credentials[$cred_id])){ + continue; + } + + $handle = $meta['user']; + if(!isset($handle_to_login[$handle])){ + continue; + } + $wp_user = get_user_by('login', $handle_to_login[$handle]); + if($wp_user === false){ + continue; + } + + $wpdb->query($wpdb->prepare( + "INSERT IGNORE INTO {$wpdb->wwa_credentials} + (credential_id, user_id, registered_blog_id, credential_source, user_handle, human_name, authenticator_type, usernameless, added, last_used) + VALUES (%s, %d, %d, %s, %s, %s, %s, %d, %s, %s)", + $cred_id, + $wp_user->ID, + $blog_id, + wp_json_encode($credentials[$cred_id]), + $handle, + isset($meta['human_name']) ? $meta['human_name'] : '', + isset($meta['authenticator_type']) ? $meta['authenticator_type'] : 'none', + !empty($meta['usernameless']) ? 1 : 0, + isset($meta['added']) ? $meta['added'] : current_time('mysql'), + isset($meta['last_used']) ? $meta['last_used'] : '-' + )); + } + + $site_users = get_users(array('blog_id' => $blog_id, 'fields' => 'ID')); + foreach($site_users as $uid){ + if(get_user_option('webauthn_only', $uid) === 'true'){ + update_user_meta($uid, 'wwa_webauthn_only', 'true'); + } + } + + update_option('wwa_credentials_migrated', true); +} + +function wwa_migrate_network_options(){ + if(!is_multisite() || get_site_option('wwa_network_options') !== false){ + return; + } + + $main_site_id = get_main_site_id(); + switch_to_blog($main_site_id); + $options = get_option('wwa_options'); + restore_current_blog(); + + $network_defaults = array( + 'first_choice' => 'true', + 'ror_origins' => '', + 'user_verification' => 'false', + 'usernameless_login' => 'false', + 'allow_authenticator_type' => 'none', + 'show_authenticator_type' => 'false' + ); + $network_options = array(); + foreach($network_defaults as $key => $default){ + if(is_array($options) && isset($options[$key])){ + $network_options[$key] = $options[$key]; + }else{ + $network_options[$key] = $default; + } + } + + update_site_option('wwa_network_options', $network_options); +} + register_activation_hook(__FILE__, 'wwa_init'); +register_uninstall_hook(__FILE__, 'wwa_uninstall'); function wwa_init(){ if(version_compare(get_bloginfo('version'), '5.0', '<')){ - deactivate_plugins(basename(__FILE__)); //disable + deactivate_plugins(basename(__FILE__)); + return; + } + wwa_create_table(); + if(is_multisite()){ + $sites = get_sites(array('fields' => 'ids', 'number' => 0)); + foreach($sites as $blog_id){ + switch_to_blog($blog_id); + wwa_init_data(); + wwa_apply_rewrite_rules(); + restore_current_blog(); + } }else{ wwa_init_data(); } } -wwa_init_data(); +function wwa_uninstall(){ + global $wpdb; + + if(is_multisite()){ + $sites = get_sites(array('fields' => 'ids', 'number' => 0)); + foreach($sites as $blog_id){ + switch_to_blog($blog_id); + wwa_uninstall_site(); + restore_current_blog(); + } + delete_site_option('wwa_network_options'); + delete_site_option('wwa_all_sites_migrated'); + }else{ + wwa_uninstall_site(); + } + + $wpdb->query("DROP TABLE IF EXISTS {$wpdb->wwa_credentials}"); + $wpdb->delete($wpdb->usermeta, array('meta_key' => 'wwa_user_handle')); + $wpdb->delete($wpdb->usermeta, array('meta_key' => 'wwa_webauthn_only')); +} + +function wwa_uninstall_site(){ + delete_option('wwa_options'); + delete_option('wwa_version'); + delete_option('wwa_log'); + delete_option('wwa_init'); + delete_option('wwa_credentials_migrated'); +} + +add_action('plugins_loaded', 'wwa_init_data'); function wwa_init_data(){ if(!get_option('wwa_init')){ // Init + wwa_create_table(); $site_domain = wp_parse_url(site_url(), PHP_URL_HOST); $wwa_init_options = array( - 'user_credentials' => '{}', - 'user_credentials_meta' => '{}', - 'user_id' => array(), - 'first_choice' => 'true', 'website_name' => get_bloginfo('name'), 'website_domain' => $site_domain === NULL ? "" : $site_domain, 'remember_me' => 'false', 'email_login' => 'false', - 'user_verification' => 'false', - 'usernameless_login' => 'false', - 'allow_authenticator_type' => 'none', 'password_reset' => 'off', 'after_user_registration' => 'none', - 'logging' => 'false' + 'logging' => 'false', + 'terminology' => 'passkey', ); + if(!is_multisite()){ + $wwa_init_options['first_choice'] = 'true'; + $wwa_init_options['user_verification'] = 'false'; + $wwa_init_options['usernameless_login'] = 'false'; + $wwa_init_options['allow_authenticator_type'] = 'none'; + $wwa_init_options['show_authenticator_type'] = 'false'; + $wwa_init_options['ror_origins'] = ''; + } update_option('wwa_options', $wwa_init_options); include('wwa-version.php'); update_option('wwa_version', $wwa_version); update_option('wwa_log', array()); update_option('wwa_init', md5(date('Y-m-d H:i:s'))); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date + add_action('wp_loaded', 'wwa_apply_rewrite_rules'); }else{ include('wwa-version.php'); if(!get_option('wwa_version') || get_option('wwa_version')['version'] != $wwa_version['version']){ - update_option('wwa_version', $wwa_version); //update version + wwa_create_table(); + wwa_migrate_credentials_for_site(); + + if(is_multisite() && !get_site_option('wwa_all_sites_migrated')){ + $all_sites = get_sites(array('fields' => 'ids', 'number' => 0)); + foreach($all_sites as $site_id){ + if(intval($site_id) === get_current_blog_id()){ + continue; + } + switch_to_blog($site_id); + if(get_option('wwa_init')){ + wwa_migrate_credentials_for_site(); + update_option('wwa_version', $wwa_version); + } + restore_current_blog(); + } + update_site_option('wwa_all_sites_migrated', true); + } + + update_option('wwa_version', $wwa_version); + add_action('wp_loaded', 'wwa_apply_rewrite_rules'); } } + + if(is_multisite()){ + wwa_migrate_network_options(); + } } // Wrap WP-WebAuthn settings function wwa_get_option($option_name){ + $network_options = array( + 'first_choice', 'ror_origins', 'user_verification', + 'usernameless_login', 'allow_authenticator_type', 'show_authenticator_type' + ); + + if(is_multisite() && in_array($option_name, $network_options, true)){ + $val = get_site_option('wwa_network_options'); + if(is_array($val) && isset($val[$option_name])){ + return $val[$option_name]; + } + return false; + } + $val = get_option('wwa_options'); if(isset($val[$option_name])){ return $val[$option_name]; @@ -73,6 +305,18 @@ function wwa_get_option($option_name){ } function wwa_update_option($option_name, $option_value){ + $network_options = array( + 'first_choice', 'ror_origins', 'user_verification', + 'usernameless_login', 'allow_authenticator_type', 'show_authenticator_type' + ); + + if(is_multisite() && in_array($option_name, $network_options, true)){ + $options = get_site_option('wwa_network_options', array()); + $options[$option_name] = $option_value; + update_site_option('wwa_network_options', $options); + return true; + } + $options = get_option('wwa_options'); $options[$option_name] = $option_value; update_option('wwa_options',$options); @@ -83,3 +327,19 @@ include('wwa-menus.php'); include('wwa-functions.php'); include('wwa-ajax.php'); include('wwa-shortcodes.php'); + +register_activation_hook(__FILE__, 'wwa_apply_rewrite_rules'); +register_deactivation_hook(__FILE__, 'wwa_deactivate'); + +function wwa_deactivate($network_wide){ + if(is_multisite() && $network_wide){ + $sites = get_sites(array('fields' => 'ids', 'number' => 0)); + foreach($sites as $blog_id){ + switch_to_blog($blog_id); + flush_rewrite_rules(); + restore_current_blog(); + } + }else{ + flush_rewrite_rules(); + } +} diff --git a/wp-content/plugins/wp-webauthn/wwa-admin-content.php b/wp-content/plugins/wp-webauthn/wwa-admin-content.php index c5fb7f99..7890593c 100644 --- a/wp-content/plugins/wp-webauthn/wwa-admin-content.php +++ b/wp-content/plugins/wp-webauthn/wwa-admin-content.php @@ -1,8 +1,13 @@ admin_url('admin-ajax.php'), + '_ajax_nonce' => wp_create_nonce('wwa_admin_ajax'), 'i18n_1' => __('User verification is disabled by default because some mobile devices do not support it (especially on Android devices). But we recommend you to enable it if possible to further secure your login.', 'wp-webauthn'), 'i18n_2' => __('Log count: ', 'wp-webauthn'), 'i18n_3' => __('Loading failed, maybe try refreshing?', 'wp-webauthn') @@ -25,7 +30,7 @@ if(!function_exists('sodium_crypto_sign_detached')){ $wwa_not_allowed = true; } if(!wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ - add_settings_error('wwa_settings', 'https_error', __('WebAuthn features are restricted to websites in secure contexts. Please make sure your website is served over HTTPS or locally with localhost.', 'wp-webauthn')); + add_settings_error('wwa_settings', 'https_error', wp_kses(__('WebAuthn features are restricted to websites in secure contexts. Please make sure your website is served over HTTPS or locally with localhost.', 'wp-webauthn'), array('code' => array()))); $wwa_not_allowed = true; } // Only admin can change settings @@ -33,17 +38,20 @@ if( (isset($_POST['wwa_ref']) && $_POST['wwa_ref'] === 'true') && check_admin_referer('wwa_options_update') && wwa_validate_privileges() - && (isset($_POST['first_choice']) && ($_POST['first_choice'] === 'true' || $_POST['first_choice'] === 'false' || $_POST['first_choice'] === 'webauthn')) + && (is_multisite() || (isset($_POST['first_choice']) && ($_POST['first_choice'] === 'true' || $_POST['first_choice'] === 'false' || $_POST['first_choice'] === 'webauthn'))) && (isset($_POST['remember_me']) && ($_POST['remember_me'] === 'true' || $_POST['remember_me'] === 'false')) && (isset($_POST['email_login']) && ($_POST['email_login'] === 'true' || $_POST['email_login'] === 'false')) - && (isset($_POST['user_verification']) && ($_POST['user_verification'] === 'true' || $_POST['user_verification'] === 'false')) - && (isset($_POST['usernameless_login']) && ($_POST['usernameless_login'] === 'true' || $_POST['usernameless_login'] === 'false')) - && (isset($_POST['allow_authenticator_type']) && ($_POST['allow_authenticator_type'] === 'none' || $_POST['allow_authenticator_type'] === 'platform' || $_POST['allow_authenticator_type'] === 'cross-platform')) + && (is_multisite() || (isset($_POST['user_verification']) && ($_POST['user_verification'] === 'true' || $_POST['user_verification'] === 'false'))) + && (is_multisite() || (isset($_POST['usernameless_login']) && ($_POST['usernameless_login'] === 'true' || $_POST['usernameless_login'] === 'false'))) + && (is_multisite() || (isset($_POST['allow_authenticator_type']) && ($_POST['allow_authenticator_type'] === 'none' || $_POST['allow_authenticator_type'] === 'platform' || $_POST['allow_authenticator_type'] === 'cross-platform'))) + && (is_multisite() || (isset($_POST['show_authenticator_type']) && ($_POST['show_authenticator_type'] === 'true' || $_POST['show_authenticator_type'] === 'false'))) && (isset($_POST['password_reset']) && ($_POST['password_reset'] === 'off' || $_POST['password_reset'] === 'admin' || $_POST['password_reset'] === 'all')) - && (isset($_POST['after_user_registration']) && ($_POST['after_user_registration'] === 'none' || $_POST['after_user_registration'] === 'login')) + && (isset($_POST['after_user_registration']) && ($_POST['after_user_registration'] === 'none' || $_POST['after_user_registration'] === 'login' || $_POST['after_user_registration'] === 'mail')) + && (isset($_POST['terminology']) && ($_POST['terminology'] === 'webauthn' || $_POST['terminology'] === 'passkey')) && (isset($_POST['logging']) && ($_POST['logging'] === 'true' || $_POST['logging'] === 'false')) && isset($_POST['website_name']) && isset($_POST['website_domain']) + // && (is_multisite() || isset($_POST['ror_origins'])) ){ $res_id = wwa_generate_random_string(5); @@ -63,16 +71,22 @@ if( wwa_add_log($res_id, 'Warning: Not in security context', true); } wwa_add_log($res_id, 'PHP Version => '.phpversion().', WordPress Version => '.get_bloginfo('version').', WP-WebAuthn Version => '.get_option('wwa_version')['version'], true); - wwa_add_log($res_id, 'Current config: first_choice => "'.wwa_get_option('first_choice').'", website_name => "'.wwa_get_option('website_name').'", website_domain => "'.wwa_get_option('website_domain').'", remember_me => "'.wwa_get_option('remember_me').'", email_login => "'.wwa_get_option('email_login').'", user_verification => "'.wwa_get_option('user_verification').'", allow_authenticator_type => "'.wwa_get_option('allow_authenticator_type').'", usernameless_login => "'.wwa_get_option('usernameless_login').'", password_reset => "'.wwa_get_option('password_reset').'", after_user_registration => "'.wwa_get_option('after_user_registration').'"', true); + wwa_add_log($res_id, 'Current config: first_choice => "'.wwa_get_option('first_choice').'", website_name => "'.wwa_get_option('website_name').'", website_domain => "'.wwa_get_option('website_domain').'", remember_me => "'.wwa_get_option('remember_me').'", email_login => "'.wwa_get_option('email_login').'", user_verification => "'.wwa_get_option('user_verification').'", allow_authenticator_type => "'.wwa_get_option('allow_authenticator_type').'", show_authenticator_type => "'.wwa_get_option('show_authenticator_type').'", usernameless_login => "'.wwa_get_option('usernameless_login').'", password_reset => "'.wwa_get_option('password_reset').'", after_user_registration => "'.wwa_get_option('after_user_registration').'", terminology => "'.wwa_get_option('terminology').'", ror_origins => "'.str_replace("\n", ', ', wwa_get_option('ror_origins')).'"', true); + $extra_logger_info = apply_filters('wwa_logger_init', array()); + foreach($extra_logger_info as $info){ + wwa_add_log($res_id, $info, true); + } wwa_add_log($res_id, 'Logger initialized', true); } wwa_update_option('logging', $post_logging); - $post_first_choice = sanitize_text_field(wp_unslash($_POST['first_choice'])); - if($post_first_choice !== wwa_get_option('first_choice')){ - wwa_add_log($res_id, 'first_choice: "'.wwa_get_option('first_choice').'"->"'.$post_first_choice.'"'); + if(!is_multisite()){ + $post_first_choice = sanitize_text_field(wp_unslash($_POST['first_choice'])); + if($post_first_choice !== wwa_get_option('first_choice')){ + wwa_add_log($res_id, 'first_choice: "'.wwa_get_option('first_choice').'"->"'.$post_first_choice.'"'); + } + wwa_update_option('first_choice', $post_first_choice); } - wwa_update_option('first_choice', $post_first_choice); $post_website_name = sanitize_text_field(wp_unslash($_POST['website_name'])); if($post_website_name !== wwa_get_option('website_name')){ @@ -86,6 +100,31 @@ if( } wwa_update_option('website_domain', $post_website_domain); + if(!is_multisite() && isset($_POST['ror_origins'])){ + $raw_ror = wp_unslash($_POST['ror_origins']); + $ror_lines = explode("\n", $raw_ror); + $sanitized_ror = array(); + foreach($ror_lines as $line){ + $line = trim($line); + if($line === ''){ + continue; + } + $parsed = wp_parse_url($line); + if(isset($parsed['scheme']) && isset($parsed['host'])){ + $origin = $parsed['scheme'] . '://' . $parsed['host']; + if(isset($parsed['port'])){ + $origin .= ':' . $parsed['port']; + } + $sanitized_ror[] = $origin; + } + } + $post_ror_origins = implode("\n", $sanitized_ror); + if($post_ror_origins !== wwa_get_option('ror_origins')){ + wwa_add_log($res_id, 'ror_origins: "'.str_replace("\n", ', ', wwa_get_option('ror_origins')).'"->"'.str_replace("\n", ', ', $post_ror_origins).'"'); + } + wwa_update_option('ror_origins', $post_ror_origins); + } + $post_remember_me = sanitize_text_field(wp_unslash($_POST['remember_me'])); if($post_remember_me !== wwa_get_option('remember_me')){ wwa_add_log($res_id, 'remember_me: "'.wwa_get_option('remember_me').'"->"'.$post_remember_me.'"'); @@ -98,23 +137,31 @@ if( } wwa_update_option('email_login', $post_email_login); - $post_user_verification = sanitize_text_field(wp_unslash($_POST['user_verification'])); - if($post_user_verification !== wwa_get_option('user_verification')){ - wwa_add_log($res_id, 'user_verification: "'.wwa_get_option('user_verification').'"->"'.$post_user_verification.'"'); - } - wwa_update_option('user_verification', $post_user_verification); + if(!is_multisite()){ + $post_user_verification = sanitize_text_field(wp_unslash($_POST['user_verification'])); + if($post_user_verification !== wwa_get_option('user_verification')){ + wwa_add_log($res_id, 'user_verification: "'.wwa_get_option('user_verification').'"->"'.$post_user_verification.'"'); + } + wwa_update_option('user_verification', $post_user_verification); - $post_allow_authenticator_type = sanitize_text_field(wp_unslash($_POST['allow_authenticator_type'])); - if($post_allow_authenticator_type !== wwa_get_option('allow_authenticator_type')){ - wwa_add_log($res_id, 'allow_authenticator_type: "'.wwa_get_option('allow_authenticator_type').'"->"'.$post_allow_authenticator_type.'"'); - } - wwa_update_option('allow_authenticator_type', $post_allow_authenticator_type); + $post_allow_authenticator_type = sanitize_text_field(wp_unslash($_POST['allow_authenticator_type'])); + if($post_allow_authenticator_type !== wwa_get_option('allow_authenticator_type')){ + wwa_add_log($res_id, 'allow_authenticator_type: "'.wwa_get_option('allow_authenticator_type').'"->"'.$post_allow_authenticator_type.'"'); + } + wwa_update_option('allow_authenticator_type', $post_allow_authenticator_type); - $post_usernameless_login = sanitize_text_field(wp_unslash($_POST['usernameless_login'])); - if($post_usernameless_login !== wwa_get_option('usernameless_login')){ - wwa_add_log($res_id, 'usernameless_login: "'.wwa_get_option('usernameless_login').'"->"'.$post_usernameless_login.'"'); + $post_show_authenticator_type = sanitize_text_field(wp_unslash($_POST['show_authenticator_type'])); + if($post_show_authenticator_type !== wwa_get_option('show_authenticator_type')){ + wwa_add_log($res_id, 'show_authenticator_type: "'.wwa_get_option('show_authenticator_type').'"->"'.$post_show_authenticator_type.'"'); + } + wwa_update_option('show_authenticator_type', $post_show_authenticator_type); + + $post_usernameless_login = sanitize_text_field(wp_unslash($_POST['usernameless_login'])); + if($post_usernameless_login !== wwa_get_option('usernameless_login')){ + wwa_add_log($res_id, 'usernameless_login: "'.wwa_get_option('usernameless_login').'"->"'.$post_usernameless_login.'"'); + } + wwa_update_option('usernameless_login', $post_usernameless_login); } - wwa_update_option('usernameless_login', $post_usernameless_login); $post_password_reset = sanitize_text_field(wp_unslash($_POST['password_reset'])); if($post_password_reset !== wwa_get_option('password_reset')){ @@ -128,13 +175,35 @@ if( } wwa_update_option('after_user_registration', $post_after_user_registration); + $post_terminology = sanitize_text_field(wp_unslash($_POST['terminology'])); + if($post_terminology !== wwa_get_option('terminology')){ + wwa_add_log($res_id, 'terminology: "'.wwa_get_option('terminology').'"->"'.$post_terminology.'"'); + } + wwa_update_option('terminology', $post_terminology); + + do_action('wwa_save_settings', $res_id); + add_settings_error('wwa_settings', 'save_success', __('Settings saved.', 'wp-webauthn'), 'success'); }elseif((isset($_POST['wwa_ref']) && $_POST['wwa_ref'] === 'true')){ add_settings_error('wwa_settings', 'save_error', __('Settings NOT saved.', 'wp-webauthn')); } settings_errors('wwa_settings'); - -wp_localize_script('wwa_admin', 'configs', array('usernameless' => (wwa_get_option('usernameless_login') === false ? 'false' : wwa_get_option('usernameless_login')), 'allow_authenticator_type' => (wwa_get_option('allow_authenticator_type') === false ? 'none' : wwa_get_option('allow_authenticator_type')))); +?> + +
+

', + '' + ), + array('a' => array('href' => array())) + ); + ?>

+
+ @@ -144,37 +213,73 @@ wp_nonce_field('wwa_options_update'); ?> + - + + - + - - - - - + + + + + + + + + + + + + + + - + + - + - + - + + + + + + - + - + + - + @@ -315,12 +454,12 @@ if($wwa_v_log === false){ if(wwa_get_option('logging') === 'true' || ($log !== false && count($log) > 0)){ ?> id="wwa-remove-log"> -

- -

+

+ +


-

your profile.', 'wp-webauthn'), admin_url('profile.php'));?>

+

your profile.', 'wp-webauthn'), esc_url(admin_url('profile.php'))), array('a' => array('href' => array())));?>

diff --git a/wp-content/plugins/wp-webauthn/wwa-ajax.php b/wp-content/plugins/wp-webauthn/wwa-ajax.php index a9a658b2..30e68809 100644 --- a/wp-content/plugins/wp-webauthn/wwa-ajax.php +++ b/wp-content/plugins/wp-webauthn/wwa-ajax.php @@ -1,4 +1,8 @@ read(); - if(isset($data[base64_encode($publicKeyCredentialId)])){ - return PublicKeyCredentialSource::createFromArray($data[base64_encode($publicKeyCredentialId)]); + global $wpdb; + $key = base64_encode($publicKeyCredentialId); + + $row = $wpdb->get_row($wpdb->prepare( + "SELECT credential_source FROM {$wpdb->wwa_credentials} WHERE credential_id = %s", + $key + )); + if($row !== null){ + $decoded = json_decode($row->credential_source, true); + if(is_array($decoded)){ + try { + return PublicKeyCredentialSource::createFromArray($decoded); + } catch(\Throwable $e) { + return null; + } + } + return null; } + + if(!get_option('wwa_credentials_migrated')){ + $old = get_option('wwa_options'); + if(isset($old['user_credentials'])){ + $data = json_decode($old['user_credentials'], true); + if(is_array($data) && isset($data[$key]) && is_array($data[$key])){ + try { + return PublicKeyCredentialSource::createFromArray($data[$key]); + } catch(\Throwable $e) { + return null; + } + } + } + } + return null; } - // Get one credential's meta by credential ID public function findOneMetaByCredentialId(string $publicKeyCredentialId): ?array { - $meta = json_decode(wwa_get_option("user_credentials_meta"), true); - if(isset($meta[base64_encode($publicKeyCredentialId)])){ - return $meta[base64_encode($publicKeyCredentialId)]; + global $wpdb; + $key = base64_encode($publicKeyCredentialId); + + $row = $wpdb->get_row($wpdb->prepare( + "SELECT user_handle, human_name, authenticator_type, usernameless, added, last_used + FROM {$wpdb->wwa_credentials} WHERE credential_id = %s", + $key + )); + if($row !== null){ + return array( + 'human_name' => $row->human_name, + 'added' => $row->added, + 'authenticator_type' => $row->authenticator_type, + 'user' => $row->user_handle, + 'usernameless' => (bool) $row->usernameless, + 'last_used' => $row->last_used, + ); } + + if(!get_option('wwa_credentials_migrated')){ + $old = get_option('wwa_options'); + if(isset($old['user_credentials_meta'])){ + $meta = json_decode($old['user_credentials_meta'], true); + if(is_array($meta) && isset($meta[$key])){ + return $meta[$key]; + } + } + } + return null; } - // Get all credentials of one user - public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array { + public function findAllForUserEntityByUserId(int $wp_user_id): array { + global $wpdb; + $rows = $wpdb->get_results($wpdb->prepare( + "SELECT credential_source FROM {$wpdb->wwa_credentials} WHERE user_id = %d", + $wp_user_id + )); $sources = []; - foreach($this->read() as $data){ - $source = PublicKeyCredentialSource::createFromArray($data); - if($source->getUserHandle() === $publicKeyCredentialUserEntity->getId()){ - $sources[] = $source; + foreach($rows as $row){ + $decoded = json_decode($row->credential_source, true); + if(!is_array($decoded)){ + continue; + } + try { + $sources[] = PublicKeyCredentialSource::createFromArray($decoded); + } catch(\Throwable $e) { + continue; } } return $sources; } - public function findCredentialsForUserEntityByType(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity, string $credentialType): array { - $credentialsForUserEntity = $this->findAllForUserEntity($publicKeyCredentialUserEntity); - $credentialsByType = []; - foreach($credentialsForUserEntity as $credential){ - if($this->findOneMetaByCredentialId($credential->getPublicKeyCredentialId())["authenticator_type"] === $credentialType){ - $credentialsByType[] = $credential; + public function findAllForUserEntity(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array { + global $wpdb; + $handle = $publicKeyCredentialUserEntity->getId(); + + $wp_user_id = $wpdb->get_var($wpdb->prepare( + "SELECT user_id FROM {$wpdb->usermeta} WHERE meta_key = 'wwa_user_handle' AND meta_value = %s LIMIT 1", + $handle + )); + if($wp_user_id !== null){ + return $this->findAllForUserEntityByUserId(intval($wp_user_id)); + } + + $rows = $wpdb->get_results($wpdb->prepare( + "SELECT credential_source FROM {$wpdb->wwa_credentials} WHERE user_handle = %s", + $handle + )); + $sources = []; + foreach($rows as $row){ + $decoded = json_decode($row->credential_source, true); + if(!is_array($decoded)){ + continue; + } + try { + $sources[] = PublicKeyCredentialSource::createFromArray($decoded); + } catch(\Throwable $e) { + continue; } } - return $credentialsByType; + return $sources; } - // Save credential into database - public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource, bool $usernameless = false): void { - $data = $this->read(); - $data_key = base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId()); - $data[$data_key] = $publicKeyCredentialSource; - $this->write($data, $data_key, $usernameless); - } - - // Update credential's last used - public function updateCredentialLastUsed(string $publicKeyCredentialId): void { - $credential = $this->findOneMetaByCredentialId($publicKeyCredentialId); - if($credential !== null){ - $credential["last_used"] = date('Y-m-d H:i:s', current_time('timestamp')); // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date - $meta = json_decode(wwa_get_option("user_credentials_meta"), true); - $meta[base64_encode($publicKeyCredentialId)] = $credential; - wwa_update_option("user_credentials_meta", wp_json_encode($meta)); - } - } - - // List all authenticators for the front-end - public function getShowList(PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity): array { - $data = json_decode(wwa_get_option("user_credentials_meta"), true); - $arr = array(); - $user_id = $publicKeyCredentialUserEntity->getId(); - foreach($data as $key => $value){ - if($user_id === $value["user"]){ - array_push($arr, array( - "key" => rtrim(strtr(base64_encode($key), '+/', '-_'), '='), - "name" => base64_decode($value["human_name"]), - "type" => $value["authenticator_type"], - "added" => $value["added"], - "usernameless" => isset($value["usernameless"]) ? $value["usernameless"] : false, - "last_used" => isset($value["last_used"]) ? $value["last_used"] : "-" - )); + public function findCredentialsForUserEntityByType(int $wp_user_id, string $credentialType): array { + global $wpdb; + $rows = $wpdb->get_results($wpdb->prepare( + "SELECT credential_source FROM {$wpdb->wwa_credentials} + WHERE user_id = %d AND authenticator_type = %s", + $wp_user_id, $credentialType + )); + $sources = []; + foreach($rows as $row){ + $decoded = json_decode($row->credential_source, true); + if(!is_array($decoded)){ + continue; + } + try { + $sources[] = PublicKeyCredentialSource::createFromArray($decoded); + } catch(\Throwable $e) { + continue; } } - return array_map(function($item){return array("key" => $item["key"], "name" => esc_html($item["name"]), "type" => $item["type"], "added" => $item["added"], "usernameless" => $item["usernameless"], "last_used" => $item["last_used"]);}, $arr); + return $sources; } - // Modify an authenticator - public function modifyAuthenticator(string $id, string $name, PublicKeyCredentialUserEntity $publicKeyCredentialUserEntity, string $action, string $res_id): string { - $keys = $this->findAllForUserEntity($publicKeyCredentialUserEntity); - $user_id = $publicKeyCredentialUserEntity->getId(); - - // Check if the user has the authenticator - foreach($keys as $item){ - if($item->getUserHandle() === $user_id){ - if(base64_encode($item->getPublicKeyCredentialId()) === base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT))){ - if($action === "rename"){ - $this->renameCredential(base64_encode($item->getPublicKeyCredentialId()), $name, $res_id); - }elseif($action === "remove"){ - $this->removeCredential(base64_encode($item->getPublicKeyCredentialId()), $res_id); - } - wwa_add_log($res_id, "ajax_modify_authenticator: Done"); - return "true"; - } - } - } - wwa_add_log($res_id, "ajax_modify_authenticator: (ERROR)Authenticator not found, exit"); - return "Not Found."; + public function setRegistrationContext(int $user_id, string $name, string $type, bool $usernameless = false): void { + $this->registration_context = compact('user_id', 'name', 'type', 'usernameless'); } - // Rename a credential from database by credential ID - private function renameCredential(string $id, string $name, string $res_id): void { - $meta = json_decode(wwa_get_option("user_credentials_meta"), true); - wwa_add_log($res_id, "ajax_modify_authenticator: Rename \"".base64_decode($meta[$id]["human_name"])."\" -> \"".$name."\""); - $meta[$id]["human_name"] = base64_encode($name); - wwa_update_option("user_credentials_meta", wp_json_encode($meta)); - } + public function saveCredentialSource(PublicKeyCredentialSource $publicKeyCredentialSource): void { + global $wpdb; + $cred_id = base64_encode($publicKeyCredentialSource->getPublicKeyCredentialId()); - // Remove a credential from database by credential ID - private function removeCredential(string $id, string $res_id): void { - $data = $this->read(); - unset($data[$id]); - $this->write($data, ''); - $meta = json_decode(wwa_get_option("user_credentials_meta"), true); - wwa_add_log($res_id, "ajax_modify_authenticator: Remove \"".base64_decode($meta[$id]["human_name"])."\""); - unset($meta[$id]); - wwa_update_option("user_credentials_meta", wp_json_encode($meta)); - } + $exists = $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) FROM {$wpdb->wwa_credentials} WHERE credential_id = %s", + $cred_id + )); - // Read credential database - private function read(): array { - if(wwa_get_option("user_credentials") !== NULL){ - try{ - return json_decode(wwa_get_option("user_credentials"), true); - }catch(\Throwable $exception) { - return []; - } - } - return []; - } - - // Save credentials data - private function write(array $data, string $key, bool $usernameless = false): void { - if(isset($_POST["name"]) && isset($_POST["type"]) && ($_POST["type"] === "platform" || $_POST["type"] == "cross-platform" || $_POST["type"] === "none") && $key !== ''){ - // Save credentials's meta separately - $source = $data[$key]->getUserHandle(); - $meta = json_decode(wwa_get_option("user_credentials_meta"), true); - $meta[$key] = array( - "human_name" => base64_encode(sanitize_text_field(wp_unslash($_POST["name"]))), - "added" => date('Y-m-d H:i:s', current_time('timestamp')), // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date - "authenticator_type" => sanitize_text_field(wp_unslash($_POST["type"])), - "user" => $source, - "usernameless" => $usernameless, - "last_used" => "-" + if($exists > 0){ + $wpdb->update( + $wpdb->wwa_credentials, + array('credential_source' => wp_json_encode($publicKeyCredentialSource)), + array('credential_id' => $cred_id) ); - wwa_update_option("user_credentials_meta", wp_json_encode($meta)); + return; } - wwa_update_option("user_credentials", wp_json_encode($data)); + + if($this->registration_context === null){ + return; + } + + $ctx = $this->registration_context; + $wpdb->insert($wpdb->wwa_credentials, array( + 'credential_id' => $cred_id, + 'user_id' => $ctx['user_id'], + 'registered_blog_id' => get_current_blog_id(), + 'credential_source' => wp_json_encode($publicKeyCredentialSource), + 'user_handle' => $publicKeyCredentialSource->getUserHandle(), + 'human_name' => base64_encode(sanitize_text_field($ctx['name'])), + 'authenticator_type' => sanitize_text_field($ctx['type']), + 'usernameless' => $ctx['usernameless'] ? 1 : 0, + 'added' => current_time('mysql'), + 'last_used' => '-', + )); + $this->registration_context = null; + } + + public function updateCredentialLastUsed(string $publicKeyCredentialId): void { + global $wpdb; + $wpdb->update( + $wpdb->wwa_credentials, + array('last_used' => current_time('mysql')), + array('credential_id' => base64_encode($publicKeyCredentialId)) + ); + } + + public function getShowListByUserId(int $wp_user_id): array { + global $wpdb; + $rows = $wpdb->get_results($wpdb->prepare( + "SELECT credential_id, human_name, authenticator_type, added, usernameless, last_used + FROM {$wpdb->wwa_credentials} + WHERE user_id = %d AND registered_blog_id = %d + ORDER BY added ASC", + $wp_user_id, get_current_blog_id() + )); + return array_map(function($row){ + return array( + 'key' => rtrim(strtr($row->credential_id, '+/', '-_'), '='), + 'name' => esc_html(base64_decode($row->human_name)), + 'type' => $row->authenticator_type, + 'added' => $row->added, + 'usernameless' => (bool) $row->usernameless, + 'last_used' => $row->last_used, + ); + }, $rows); + } + + public function renameCredential(string $credential_id_urlsafe, int $wp_user_id, string $new_name, string $res_id): bool { + global $wpdb; + $credential_id = base64_encode(base64_decode(strtr($credential_id_urlsafe, '-_', '+/'))); + wwa_add_log($res_id, "ajax_modify_authenticator: Rename credential"); + $affected = $wpdb->update( + $wpdb->wwa_credentials, + array('human_name' => base64_encode(sanitize_text_field($new_name))), + array('credential_id' => $credential_id, 'user_id' => $wp_user_id, 'registered_blog_id' => get_current_blog_id()) + ); + return $affected !== false; + } + + public function removeCredential(string $credential_id_urlsafe, int $wp_user_id, string $res_id): bool { + global $wpdb; + $credential_id = base64_encode(base64_decode(strtr($credential_id_urlsafe, '-_', '+/'))); + wwa_add_log($res_id, "ajax_modify_authenticator: Remove credential"); + $affected = $wpdb->delete( + $wpdb->wwa_credentials, + array('credential_id' => $credential_id, 'user_id' => $wp_user_id, 'registered_blog_id' => get_current_blog_id()) + ); + return $affected > 0; } } // Bind an authenticator function wwa_ajax_create(){ + check_ajax_referer('wwa_ajax'); + $client_id = false; try{ $res_id = wwa_generate_random_string(5); $client_id = strval(time()).wwa_generate_random_string(24); @@ -260,15 +352,17 @@ function wwa_ajax_create(){ wwa_add_log($res_id, "ajax_create: user => \"".$user_info->user_login."\""); // Get user ID or create one - $user_key = ""; - if(!isset(wwa_get_option("user_id")[$user_info->user_login])){ - wwa_add_log($res_id, "ajax_create: User not initialized, initialize"); - $user_array = wwa_get_option("user_id"); - $user_key = hash("sha256", $user_info->user_login."-".$user_info->display_name."-".wwa_generate_random_string(10)); - $user_array[$user_info->user_login] = $user_key; - wwa_update_option("user_id", $user_array); - }else{ - $user_key = wwa_get_option("user_id")[$user_info->user_login]; + $user_key = get_user_meta($user_info->ID, 'wwa_user_handle', true); + if(!$user_key){ + $user_id_map = wwa_get_option("user_id"); + if(is_array($user_id_map) && isset($user_id_map[$user_info->user_login])){ + $user_key = $user_id_map[$user_info->user_login]; + update_user_meta($user_info->ID, 'wwa_user_handle', $user_key); + }else{ + wwa_add_log($res_id, "ajax_create: User not initialized, initialize"); + $user_key = hash("sha256", $user_info->user_login."-".$user_info->display_name."-".wwa_generate_random_string(10)); + update_user_meta($user_info->ID, 'wwa_user_handle', $user_key); + } } $user = array( @@ -287,7 +381,7 @@ function wwa_ajax_create(){ $credentialSourceRepository = new PublicKeyCredentialSourceRepository(); - $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity); + $credentialSources = $credentialSourceRepository->findAllForUserEntityByUserId($user_info->ID); // Convert the Credential Sources into Public Key Credential Descriptors for excluding $excludeCredentials = array_map(function (PublicKeyCredentialSource $credential) { @@ -363,6 +457,7 @@ add_action("wp_ajax_wwa_create" , "wwa_ajax_create"); // Verify the attestation function wwa_ajax_create_response(){ + check_ajax_referer('wwa_ajax'); $client_id = false; try{ $res_id = wwa_generate_random_string(5); @@ -400,7 +495,7 @@ function wwa_ajax_create_response(){ $wwa_post["type"] = sanitize_text_field(wp_unslash($_POST["type"])); $wwa_post["usernameless"] = sanitize_text_field(wp_unslash($_POST["usernameless"])); wwa_add_log($res_id, "ajax_create_response: name => \"".$wwa_post["name"]."\", type => \"".$wwa_post["type"]."\", usernameless => \"".$wwa_post["usernameless"]."\""); - wwa_add_log($res_id, "ajax_create_response: data => ".base64_decode(sanitize_text_field(wp_unslash($_POST["data"])))); + wwa_add_log($res_id, "ajax_create_response: data => ".sanitize_text_field(base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))))); } if(isset($_POST["user_id"])){ @@ -487,13 +582,26 @@ function wwa_ajax_create_response(){ try { $publicKeyCredentialSource = $server->loadAndCheckAttestationResponse( base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))), - unserialize(base64_decode($temp_val["pkcco"])), + unserialize(base64_decode($temp_val["pkcco"]), ['allowed_classes' => [ + Webauthn\PublicKeyCredentialCreationOptions::class, + Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs::class, + Webauthn\PublicKeyCredentialRpEntity::class, + Webauthn\PublicKeyCredentialUserEntity::class, + Webauthn\AuthenticatorSelectionCriteria::class, + ]]), $serverRequest ); wwa_add_log($res_id, "ajax_create_response: Challenge verified"); - $publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource, $temp_val["bind_config"]["usernameless"]); + $user_info = isset($_POST["user_id"]) ? get_user_by('id', intval(sanitize_text_field(wp_unslash($_POST["user_id"])))) : wp_get_current_user(); + $publicKeyCredentialSourceRepository->setRegistrationContext( + $user_info->ID, + $wwa_post["name"], + $wwa_post["type"], + $temp_val["bind_config"]["usernameless"] + ); + $publicKeyCredentialSourceRepository->saveCredentialSource($publicKeyCredentialSource); if($temp_val["bind_config"]["usernameless"]){ wwa_add_log($res_id, "ajax_create_response: Authenticator added with usernameless authentication feature"); @@ -530,13 +638,15 @@ add_action("wp_ajax_wwa_create_response" , "wwa_ajax_create_response"); // Auth challenge function wwa_ajax_auth_start(){ + $client_id = false; try{ $res_id = wwa_generate_random_string(5); $client_id = strval(time()).wwa_generate_random_string(24); wwa_init_new_options(); - wwa_add_log($res_id, "ajax_auth: Start"); + $is_conditional = isset($_GET['conditional']) && $_GET['conditional'] === 'true'; + wwa_add_log($res_id, "ajax_auth: Start" . ($is_conditional ? " (conditional)" : "")); // Check queries if(!isset($_GET["type"])){ @@ -598,13 +708,17 @@ function wwa_ajax_auth_start(){ wwa_add_log($res_id, "ajax_auth: type => \"test\", user => \"".$user_info->user_login."\", usernameless => \"false\""); - if(!isset(wwa_get_option("user_id")[$user_info->user_login])){ - wwa_add_log($res_id, "ajax_auth: (ERROR)User not initialized, exit"); - wwa_wp_die("User not inited.", $client_id); - }else{ - $user_key = wwa_get_option("user_id")[$user_info->user_login]; - $user_icon = get_avatar_url($user_info->user_email, array("scheme" => "https")); + $user_key = get_user_meta($user_info->ID, 'wwa_user_handle', true); + if(!$user_key){ + $user_id_map = wwa_get_option("user_id"); + if(is_array($user_id_map) && isset($user_id_map[$user_info->user_login])){ + $user_key = $user_id_map[$user_info->user_login]; + }else{ + wwa_add_log($res_id, "ajax_auth: (ERROR)User not initialized, exit"); + wwa_wp_die("User not inited.", $client_id); + } } + $user_icon = get_avatar_url($user_info->user_email, array("scheme" => "https")); }else{ if(wwa_get_option("usernameless_login") === "true"){ wwa_add_log($res_id, "ajax_auth: type => \"test\", usernameless => \"true\""); @@ -629,12 +743,16 @@ function wwa_ajax_auth_start(){ $user_info = $wp_user; $user_icon = get_avatar_url($user_info->user_email, array("scheme" => "https")); wwa_add_log($res_id, "ajax_auth: type => \"auth\", user => \"".$user_info->user_login."\""); - if(!isset(wwa_get_option("user_id")[$user_info->user_login])){ - wwa_add_log($res_id, "ajax_auth: User found but not initialized, create a fake id"); - $user_key = hash("sha256", $wwa_get["user"]."-".$wwa_get["user"]."-".wwa_generate_random_string(10)); - $user_exist = false; - }else{ - $user_key = wwa_get_option("user_id")[$user_info->user_login]; + $user_key = get_user_meta($user_info->ID, 'wwa_user_handle', true); + if(!$user_key){ + $user_id_map = wwa_get_option("user_id"); + if(is_array($user_id_map) && isset($user_id_map[$user_info->user_login])){ + $user_key = $user_id_map[$user_info->user_login]; + }else{ + wwa_add_log($res_id, "ajax_auth: User found but not initialized, create a fake id"); + $user_key = hash("sha256", $wwa_get["user"]."-".$wwa_get["user"]."-".wwa_generate_random_string(10)); + $user_exist = false; + } } }else{ $user_info = new stdClass(); @@ -681,15 +799,40 @@ function wwa_ajax_auth_start(){ // Usernameless authentication, return empty allowed credentials list wwa_add_log($res_id, "ajax_auth: Usernameless authentication, allowedCredentials => []"); $allowedCredentials = array(); + }else if(!$user_exist){ + // User doesn't exist or hasn't bound any authenticator, + // generate deterministic fake credentials + $fake_seed = hash_hmac('sha256', $user_info->user_login, wp_salt('auth'), true); + + // Determine count: 0 => 25%, 1-5 => 15% each + $fake_count = ord($fake_seed[0]) % 20; + $fake_count = $fake_count < 5 ? 0 : intdiv($fake_count - 5, 3) + 1; + + $allowedCredentials = array(); + $id_length_ranges = [[16, 20], [32, 48], [48, 64], [64, 80], [20, 32]]; + for($i = 0; $i < $fake_count; $i++){ + $cred_seed = hash_hmac('sha512', $user_info->user_login . chr($i), wp_salt('auth'), true); + $range = $id_length_ranges[ord($cred_seed[0]) % count($id_length_ranges)]; + $id_len = $range[0] + (ord($cred_seed[1]) % ($range[1] - $range[0] + 1)); + // Use remaining bytes as credential ID, extend if needed for longer IDs + $id_bytes = substr($cred_seed, 2); + if(strlen($id_bytes) < $id_len){ + $id_bytes .= hash_hmac('sha256', $cred_seed, wp_salt('auth'), true); + } + $allowedCredentials[] = new PublicKeyCredentialDescriptor( + PublicKeyCredentialDescriptor::CREDENTIAL_TYPE_PUBLIC_KEY, + substr($id_bytes, 0, $id_len) + ); + } + wwa_add_log($res_id, "ajax_auth: User not exists, fake allowedCredentials count => ".$fake_count); }else{ // Get the list of authenticators associated to the user - // $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity); $allow_authenticator_type = wwa_get_option("allow_authenticator_type"); if($allow_authenticator_type === false || $allow_authenticator_type === "none"){ - $credentialSources = $credentialSourceRepository->findAllForUserEntity($userEntity); - }elseif($allow_authenticator_type !== false && $allow_authenticator_type !== "none"){ + $credentialSources = $credentialSourceRepository->findAllForUserEntityByUserId($user_info->ID); + }else{ wwa_add_log($res_id, "ajax_auth: allow_authenticator_type => \"".$allow_authenticator_type."\", filter authenticators"); - $credentialSources = $credentialSourceRepository->findCredentialsForUserEntityByType($userEntity, $allow_authenticator_type); + $credentialSources = $credentialSourceRepository->findCredentialsForUserEntityByType($user_info->ID, $allow_authenticator_type); } // Logged in and testing, if the user haven't bind a authenticator yet, exit @@ -768,7 +911,8 @@ function wwa_ajax_auth(){ wwa_init_new_options(); - wwa_add_log($res_id, "ajax_auth_response: Client response received"); + $is_conditional = isset($_POST['conditional']) && $_POST['conditional'] === 'true'; + wwa_add_log($res_id, "ajax_auth_response: Client response received" . ($is_conditional ? " (conditional)" : "")); if(!isset($_POST["clientid"])){ wwa_add_log($res_id, "ajax_auth_response: (ERROR)Missing parameters, exit"); @@ -823,7 +967,7 @@ function wwa_ajax_auth(){ wwa_wp_die("Bad request.", $client_id); } - $temp_val["usernameless_auth"] = unserialize($temp_val["usernameless_auth"]); + $temp_val["usernameless_auth"] = unserialize($temp_val["usernameless_auth"], ['allowed_classes' => false]); if($temp_val["usernameless_auth"] === false && $temp_val["user_name_auth"] === false){ wwa_add_log($res_id, "ajax_auth_response: (ERROR)Username not found in transient, exit"); @@ -878,13 +1022,17 @@ function wwa_ajax_auth(){ } } - if(!isset(wwa_get_option("user_id")[$user_info->user_login])){ - wwa_add_log($res_id, "ajax_auth_response: (ERROR)User not initialized, exit"); - wwa_wp_die("User not inited.", $client_id); - }else{ - $user_key = wwa_get_option("user_id")[$user_info->user_login]; - $user_icon = get_avatar_url($user_info->user_email, array("scheme" => "https")); + $user_key = get_user_meta($user_info->ID, 'wwa_user_handle', true); + if(!$user_key){ + $user_id_map = wwa_get_option("user_id"); + if(is_array($user_id_map) && isset($user_id_map[$user_info->user_login])){ + $user_key = $user_id_map[$user_info->user_login]; + }else{ + wwa_add_log($res_id, "ajax_auth_response: (ERROR)User not initialized, exit"); + wwa_wp_die("User not inited.", $client_id); + } } + $user_icon = get_avatar_url($user_info->user_email, array("scheme" => "https")); $userEntity = new PublicKeyCredentialUserEntity( $user_info->user_login, @@ -903,7 +1051,7 @@ function wwa_ajax_auth(){ } wwa_add_log($res_id, "ajax_auth_response: type => \"".$wwa_post["type"]."\""); - wwa_add_log($res_id, "ajax_auth_response: Usernameless authentication, try to find user by credential_id => \"".$data_array["rawId"]."\", userHandle => \"".$data_array["response"]["userHandle"]."\""); + wwa_add_log($res_id, "ajax_auth_response: Usernameless authentication, try to find user by credential_id => \"".sanitize_text_field($data_array["rawId"])."\""); $credential_meta = $publicKeyCredentialSourceRepository->findOneMetaByCredentialId(base64_decode($data_array["rawId"])); @@ -911,59 +1059,66 @@ function wwa_ajax_auth(){ $allow_authenticator_type = wwa_get_option("allow_authenticator_type"); if($allow_authenticator_type !== false && $allow_authenticator_type !== 'none'){ if($credential_meta["authenticator_type"] !== $allow_authenticator_type){ - wwa_add_log($res_id, "ajax_auth_response: (ERROR)Credential type error, authenticator_type => \"".$credential_meta["authenticator_type"]."\", allow_authenticator_type => \"".$allow_authenticator_type."\", exit"); + wwa_add_log($res_id, "ajax_auth_response: (ERROR)Credential type error, exit"); wwa_wp_die("Bad request.", $client_id); } } if($credential_meta["usernameless"] === true){ - wwa_add_log($res_id, "ajax_auth_response: Credential found, usernameless => \"true\", user_key => \"".$credential_meta["user"]."\""); + global $wpdb; + $cred_row = $wpdb->get_row($wpdb->prepare( + "SELECT user_id, user_handle FROM {$wpdb->wwa_credentials} WHERE credential_id = %s", + base64_encode(base64_decode($data_array["rawId"])) + )); - // Try to find user - $all_user = wwa_get_option("user_id"); - $user_login_name = false; - foreach($all_user as $user => $user_id){ - if($user_id === $credential_meta["user"]){ - $user_login_name = $user; - break; - } - } + $resolved_user_handle = null; + $resolved_user_info = null; - // Match userHandle - if($credential_meta["user"] === base64_decode($data_array["response"]["userHandle"])){ - // Found user - if($user_login_name !== false){ - wwa_add_log($res_id, "ajax_auth_response: Found user => \"".$user_login_name."\", user_key => \"".$credential_meta["user"]."\""); - - // Testing, verify user - if($wwa_post["type"] === "test" && current_user_can('read')){ - $user_wp = wp_get_current_user(); - if($user_login_name !== $user_wp->user_login){ - wwa_add_log($res_id, "ajax_auth_response: (ERROR)Credential found, but user not match, exit"); - wwa_wp_die("Bad request.", $client_id); + if($cred_row !== null){ + $resolved_user_handle = $cred_row->user_handle; + $resolved_user_info = get_user_by('id', $cred_row->user_id); + }elseif(!get_option('wwa_credentials_migrated')){ + wwa_add_log($res_id, "ajax_auth_response: Credential not in global table, trying pre-migration fallback"); + $old_handle = $credential_meta["user"]; + $all_user = wwa_get_option("user_id"); + if(is_array($all_user)){ + foreach($all_user as $login => $handle){ + if($handle === $old_handle){ + $resolved_user_info = get_user_by('login', $login); + $resolved_user_handle = $old_handle; + break; } } - - $user_info = get_user_by('login', $user_login_name); - - if($user_info === false){ - wwa_add_log($res_id, "ajax_auth_response: (ERROR)Wrong user ID, exit"); - wwa_wp_die("Something went wrong."); - } - - $userEntity = new PublicKeyCredentialUserEntity( - $user_info->user_login, - $credential_meta["user"], - $user_info->display_name, - get_avatar_url($user_info->user_email, array("scheme" => "https")) - ); - }else{ - wwa_add_log($res_id, "ajax_auth_response: (ERROR)Credential found, but user not found, exit"); - wwa_wp_die("Bad request.", $client_id); } - }else{ - wwa_add_log($res_id, "ajax_auth_response: (ERROR)Credential found, but userHandle not matched, exit"); + } + + if($resolved_user_info === false || $resolved_user_info === null){ + wwa_add_log($res_id, "ajax_auth_response: (ERROR)User not found, exit"); wwa_wp_die("Bad request.", $client_id); } + + if($resolved_user_handle !== base64_decode($data_array["response"]["userHandle"])){ + wwa_add_log($res_id, "ajax_auth_response: (ERROR)userHandle not matched, exit"); + wwa_wp_die("Bad request.", $client_id); + } + + $user_info = $resolved_user_info; + $user_login_name = $user_info->user_login; + wwa_add_log($res_id, "ajax_auth_response: Found user => \"".$user_login_name."\""); + + if($wwa_post["type"] === "test" && current_user_can('read')){ + $user_wp = wp_get_current_user(); + if($user_login_name !== $user_wp->user_login){ + wwa_add_log($res_id, "ajax_auth_response: (ERROR)User not match, exit"); + wwa_wp_die("Bad request.", $client_id); + } + } + + $userEntity = new PublicKeyCredentialUserEntity( + $user_info->user_login, + $resolved_user_handle, + $user_info->display_name, + get_avatar_url($user_info->user_email, array("scheme" => "https")) + ); }else{ wwa_add_log($res_id, "ajax_auth_response: (ERROR)Credential found, but usernameless => \"false\", exit"); wwa_wp_die("Bad request.", $client_id); @@ -974,12 +1129,14 @@ function wwa_ajax_auth(){ } }else{ wwa_add_log($res_id, "ajax_auth_response: type => \"auth\", user => \"".$temp_val["user_name_auth"]."\""); - $userEntity = unserialize($temp_val["user_auth"]); + $userEntity = unserialize($temp_val["user_auth"], ['allowed_classes' => [ + Webauthn\PublicKeyCredentialUserEntity::class, + ]]); } } $decoded_data = base64_decode(sanitize_text_field(wp_unslash($_POST["data"]))); - wwa_add_log($res_id, "ajax_auth_response: data => ".$decoded_data); + wwa_add_log($res_id, "ajax_auth_response: data => ".sanitize_text_field($decoded_data)); if($temp_val["user_exist"]){ $rpEntity = new PublicKeyCredentialRpEntity( @@ -1004,7 +1161,11 @@ function wwa_ajax_auth(){ try { $server->loadAndCheckAssertionResponse( $decoded_data, - unserialize(base64_decode($temp_val["pkcco_auth"])), + unserialize(base64_decode($temp_val["pkcco_auth"]), ['allowed_classes' => [ + Webauthn\PublicKeyCredentialRequestOptions::class, + Webauthn\AuthenticationExtensions\AuthenticationExtensionsClientInputs::class, + Webauthn\PublicKeyCredentialDescriptor::class, + ]]), $userEntity, $serverRequest ); @@ -1084,6 +1245,7 @@ add_action("wp_ajax_nopriv_wwa_auth" , "wwa_ajax_auth"); // Get authenticator list function wwa_ajax_authenticator_list(){ + check_ajax_referer('wwa_ajax'); $res_id = wwa_generate_random_string(5); wwa_init_new_options(); @@ -1118,30 +1280,21 @@ function wwa_ajax_authenticator_list(){ header('Content-Type: application/json'); - $user_key = ""; - if(!isset(wwa_get_option("user_id")[$user_info->user_login])){ - // The user haven't bound any authenticator, return empty list + $user_key = get_user_meta($user_info->ID, 'wwa_user_handle', true); + if(!$user_key){ echo "[]"; exit; - }else{ - $user_key = wwa_get_option("user_id")[$user_info->user_login]; } - $userEntity = new PublicKeyCredentialUserEntity( - $user_info->user_login, - $user_key, - $user_info->display_name, - get_avatar_url($user_info->user_email, array("scheme" => "https")) - ); - $publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository(); - echo wp_json_encode($publicKeyCredentialSourceRepository->getShowList($userEntity)); + echo wp_json_encode($publicKeyCredentialSourceRepository->getShowListByUserId($user_info->ID)); exit; } add_action("wp_ajax_wwa_authenticator_list" , "wwa_ajax_authenticator_list"); // Modify an authenticator function wwa_ajax_modify_authenticator(){ + check_ajax_referer('wwa_ajax'); try{ $res_id = wwa_generate_random_string(5); @@ -1192,30 +1345,25 @@ function wwa_ajax_modify_authenticator(){ wwa_wp_die("Bad Request."); } - $user_key = ""; - if(!isset(wwa_get_option("user_id")[$user_info->user_login])){ - // The user haven't bound any authenticator, exit - wwa_add_log($res_id, "ajax_modify_authenticator: (ERROR)User not initialized, exit"); - wwa_wp_die("User not inited."); - }else{ - $user_key = wwa_get_option("user_id")[$user_info->user_login]; - } - - $userEntity = new PublicKeyCredentialUserEntity( - $user_info->user_login, - $user_key, - $user_info->display_name, - get_avatar_url($user_info->user_email, array("scheme" => "https")) - ); - wwa_add_log($res_id, "ajax_modify_authenticator: user => \"".$user_info->user_login."\""); $publicKeyCredentialSourceRepository = new PublicKeyCredentialSourceRepository(); if($_GET["target"] === "rename"){ - echo $publicKeyCredentialSourceRepository->modifyAuthenticator(sanitize_text_field(wp_unslash($_GET["id"])), sanitize_text_field(wp_unslash($_GET["name"])), $userEntity, "rename", $res_id); + $result = $publicKeyCredentialSourceRepository->renameCredential( + sanitize_text_field(wp_unslash($_GET["id"])), + $user_info->ID, + sanitize_text_field(wp_unslash($_GET["name"])), + $res_id + ); + echo $result ? "true" : "Not Found."; }elseif($_GET["target"] === "remove"){ - echo $publicKeyCredentialSourceRepository->modifyAuthenticator(sanitize_text_field(wp_unslash($_GET["id"])), "", $userEntity, "remove", $res_id); + $result = $publicKeyCredentialSourceRepository->removeCredential( + sanitize_text_field(wp_unslash($_GET["id"])), + $user_info->ID, + $res_id + ); + echo $result ? "true" : "Not Found."; } exit; }catch(\Exception $exception){ @@ -1234,6 +1382,7 @@ add_action("wp_ajax_wwa_modify_authenticator" , "wwa_ajax_modify_authenticator") // Print log function wwa_ajax_get_log(){ + check_ajax_referer('wwa_admin_ajax'); if(!wwa_validate_privileges()){ wwa_wp_die("Bad Request."); } @@ -1254,6 +1403,7 @@ add_action("wp_ajax_wwa_get_log" , "wwa_ajax_get_log"); // Clear log function wwa_ajax_clear_log(){ + check_ajax_referer('wwa_admin_ajax'); if(!wwa_validate_privileges()){ wwa_wp_die("Bad Request."); } diff --git a/wp-content/plugins/wp-webauthn/wwa-compatibility.php b/wp-content/plugins/wp-webauthn/wwa-compatibility.php index 57a46d61..3ee02a83 100644 --- a/wp-content/plugins/wp-webauthn/wwa-compatibility.php +++ b/wp-content/plugins/wp-webauthn/wwa-compatibility.php @@ -1,5 +1,61 @@ delete($wpdb->wwa_credentials, array( + 'user_id' => $user_id, + 'registered_blog_id' => $blog_id + )); +} + +function wwa_cleanup_all_user_credentials($user_id){ + global $wpdb; + $wpdb->delete($wpdb->wwa_credentials, array('user_id' => $user_id)); + delete_user_meta($user_id, 'wwa_user_handle'); + delete_user_meta($user_id, 'wwa_webauthn_only'); +} + function wwa_delete_user($user_id){ $res_id = wwa_generate_random_string(5); $user_data = get_userdata($user_id); - $all_user_meta = wwa_get_option('user_id'); - $user_key = ''; - - // Delete user meta - foreach($all_user_meta as $user => $id){ - if($user === $user_data->user_login){ - $user_key = $id; - wwa_add_log($res_id, "Delete user_key => \"".$id."\""); - unset($all_user_meta[$user]); - } + if($user_data !== false){ + wwa_add_log($res_id, "Deleted user credentials for => \"".$user_data->user_login."\""); } - // Delete credentials - $all_credentials_meta = json_decode(wwa_get_option('user_credentials_meta'), true); - $all_credentials = json_decode(wwa_get_option('user_credentials'), true); - foreach($all_credentials_meta as $credential => $meta){ - if($user_key === $meta['user']){ - wwa_add_log($res_id, "Delete credential => \"".$credential."\""); - unset($all_credentials_meta[$credential]); - unset($all_credentials[$credential]); - } + if(is_multisite()){ + wwa_cleanup_blog_credentials($user_id, get_current_blog_id()); + }else{ + wwa_cleanup_all_user_credentials($user_id); } - wwa_update_option('user_id', $all_user_meta); - wwa_update_option('user_credentials_meta', wp_json_encode($all_credentials_meta)); - wwa_update_option('user_credentials', wp_json_encode($all_credentials)); - wwa_add_log($res_id, "Deleted user => \"".$user_data->user_login."\""); } add_action('delete_user', 'wwa_delete_user'); +function wwa_delete_user_multisite($user_id){ + $res_id = wwa_generate_random_string(5); + + $user_data = get_userdata($user_id); + if($user_data !== false){ + wwa_add_log($res_id, "Deleted all user credentials for => \"".$user_data->user_login."\" (network deletion)"); + } + + wwa_cleanup_all_user_credentials($user_id); +} +add_action('wpmu_delete_user', 'wwa_delete_user_multisite'); + +function wwa_remove_user_from_blog($user_id, $blog_id){ + $res_id = wwa_generate_random_string(5); + + $user_data = get_userdata($user_id); + if($user_data !== false){ + wwa_add_log($res_id, "Deleted user credentials for => \"".$user_data->user_login."\" (removed from blog ".$blog_id.")"); + } + + wwa_cleanup_blog_credentials($user_id, $blog_id); +} +add_action('remove_user_from_blog', 'wwa_remove_user_from_blog', 10, 2); + // Add CSS and JS in login page function wwa_login_js(){ + wwa_init_new_options(); + $wwa_not_allowed = false; if(!function_exists('mb_substr') || !function_exists('gmp_intval') || !wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ $wwa_not_allowed = true; } wp_enqueue_script('wwa_login', plugins_url('js/login.js', __FILE__), array(), get_option('wwa_version')['version'], true); $first_choice = wwa_get_option('first_choice'); - wp_localize_script('wwa_login', 'php_vars', array( + wp_localize_script('wwa_login', 'wwa_login_php_vars', array( 'ajax_url' => admin_url('admin-ajax.php'), 'admin_url' => admin_url(), 'usernameless' => (wwa_get_option('usernameless_login') === false ? 'false' : wwa_get_option('usernameless_login')), @@ -156,8 +193,9 @@ function wwa_login_js(){ 'webauthn_only' => ($first_choice === 'webauthn' && !$wwa_not_allowed) ? 'true' : 'false', 'password_reset' => ((wwa_get_option('password_reset') === false || wwa_get_option('password_reset') === 'off') ? 'false' : 'true'), 'separator' => apply_filters('login_link_separator', ' | '), + 'terminology' => (wwa_get_option('terminology') === false ? 'passkey' : wwa_get_option('terminology')), 'i18n_1' => __('Auth', 'wp-webauthn'), - 'i18n_2' => __('Authenticate with WebAuthn', 'wp-webauthn'), + 'i18n_2' => wwa_get_option('terminology') === 'webauthn' ? __('Authenticate with WebAuthn', 'wp-webauthn') : __('Authenticate with a passkey', 'wp-webauthn'), 'i18n_3' => __('Hold on...', 'wp-webauthn'), 'i18n_4' => __('Please proceed...', 'wp-webauthn'), 'i18n_5' => __('Authenticating...', 'wp-webauthn'), @@ -167,7 +205,9 @@ function wwa_login_js(){ 'i18n_9' => __('Username', 'wp-webauthn'), 'i18n_10' => __('Username or Email Address'), 'i18n_11' => __('Error: The username field is empty.', 'wp-webauthn'), - 'i18n_12' => ''.__('Try to enter the username', 'wp-webauthn').'' + 'i18n_12' => ''.__('Try to enter the username', 'wp-webauthn').'', + 'i18n_13' => __('Password'), + 'i18n_14' => wwa_get_option('terminology') === 'webauthn' ? 'WebAuthn' : __('Passkey', 'wp-webauthn') )); if($first_choice === 'true' || $first_choice === 'webauthn'){ wp_enqueue_script('wwa_default', plugins_url('js/default_wa.js', __FILE__), array(), get_option('wwa_version')['version'], true); @@ -187,7 +227,7 @@ function wwa_disable_password($user){ if(is_wp_error($user)){ return $user; } - if(get_the_author_meta('webauthn_only', $user->ID) === 'true'){ + if(get_user_meta($user->ID, 'wwa_webauthn_only', true) === 'true'){ return new WP_Error('wwa_password_disabled_for_account', __('Logging in with password has been disabled for this account.', 'wp-webauthn')); } return $user; @@ -240,86 +280,73 @@ if(wwa_get_option('password_reset') === 'admin' || wwa_get_option('password_rese add_filter('allow_password_reset', 'wwa_handle_password'); } -// Show a notice in admin pages function wwa_no_authenticator_warning(){ + if(is_network_admin()){ + return; + } + $user_info = wp_get_current_user(); $first_choice = wwa_get_option('first_choice'); $check_self = true; - if($first_choice !== 'webauthn' && get_the_author_meta('webauthn_only', $user_info->ID ) !== 'true'){ + if($first_choice !== 'webauthn' && get_user_meta($user_info->ID, 'wwa_webauthn_only', true) !== 'true'){ $check_self = false; } if($check_self){ - // Check current user - $user_id = ''; - $show_notice_flag = false; - if(!isset(wwa_get_option('user_id')[$user_info->user_login])){ - $show_notice_flag = true; - }else{ - $user_id = wwa_get_option('user_id')[$user_info->user_login]; - } - - if(!$show_notice_flag){ - $show_notice_flag = true; - $data = json_decode(wwa_get_option('user_credentials_meta'), true); - foreach($data as $value){ - if($user_id === $value['user']){ - $show_notice_flag = false; - break; - } - } - } - - if($show_notice_flag){?> + global $wpdb; + $count = $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) FROM {$wpdb->wwa_credentials} + WHERE user_id = %d AND registered_blog_id = %d", + $user_info->ID, get_current_blog_id() + )); + if(intval($count) === 0){ ?>
- -

Register', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('your account', 'wp-webauthn'), admin_url('profile.php'));?>

+ +

Register', 'wp-webauthn'), $wwa_scope_label, $wwa_cred_label, esc_url(admin_url('profile.php'))), array('a' => array('href' => array())));?>

+ +

+
ID){ $user_id_wp = intval($_GET['user_id']); - if($user_id_wp <= 0){ + if($user_id_wp <= 0 || !current_user_can('edit_user', $user_id_wp)){ return; } - if(!current_user_can('edit_user', $user_id_wp)){ + $other_user = get_user_by('id', $user_id_wp); + if($other_user === false){ return; } - $user_info = get_user_by('id', $user_id_wp); - - if($user_info === false){ + if($first_choice !== 'webauthn' && get_user_meta($other_user->ID, 'wwa_webauthn_only', true) !== 'true'){ return; } - if($first_choice !== 'webauthn' && get_the_author_meta('webauthn_only', $user_info->ID) !== 'true'){ - return; - } - - $user_id = ''; - $show_notice_flag = false; - if(!isset(wwa_get_option('user_id')[$user_info->user_login])){ - $show_notice_flag = true; - }else{ - $user_id = wwa_get_option('user_id')[$user_info->user_login]; - } - - if(!$show_notice_flag){ - $show_notice_flag = true; - $data = json_decode(wwa_get_option('user_credentials_meta'), true); - foreach($data as $value){ - if($user_id === $value['user']){ - $show_notice_flag = false; - break; - } - } - } - - if($show_notice_flag){ ?> + global $wpdb; + $count = $wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) FROM {$wpdb->wwa_credentials} + WHERE user_id = %d AND registered_blog_id = %d", + $other_user->ID, get_current_blog_id() + )); + if(intval($count) === 0){ ?>
- -

this account haven\'t register any WebAuthn authenticator yet. This user may unable to login.', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('this account', 'wp-webauthn'));?>

+ +

this account haven\'t register any %2$s on the current site yet. This user may unable to login.', 'wp-webauthn'), $wwa_scope_label, $wwa_cred_label), array('strong' => array()));?>

+ +

+
'.__('Network Settings', 'wp-webauthn').''; + } + return $links_array; +} +if(is_multisite()){ + add_filter('network_admin_plugin_action_links', 'wwa_network_settings_link', 10, 2); +} + function wwa_meta_link($links_array, $plugin_file_name){ if($plugin_file_name === 'wp-webauthn/wp-webauthn.php'){ $links_array[] = ''.__('GitHub', 'wp-webauthn').''; - $links_array[] = ''.__('Documentation', 'wp-webauthn').''; + $links_array[] = ''.__('Documentation', 'wp-webauthn').''; } return $links_array; } add_filter('plugin_row_meta', 'wwa_meta_link', 10, 2); // Check if we are under HTTPS -function wwa_check_ssl() { +function wwa_check_ssl(){ if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off' && $_SERVER['HTTPS'] !== '') { return true; } @@ -380,17 +417,37 @@ function wwa_check_ssl() { } // Check user privileges -function wwa_validate_privileges() { - $user = wp_get_current_user(); - $allowed_roles = array('administrator'); - if(array_intersect($allowed_roles, $user->roles)){ - return true; +function wwa_validate_privileges(){ + return current_user_can('manage_options'); +} + +// Get Related Origins Request list +function wwa_get_ror_list(){ + $raw = wwa_get_option('ror_origins'); + if($raw === false || $raw === ''){ + return array(); } - return false; + $origins = array(); + $lines = explode("\n", $raw); + foreach($lines as $line){ + $line = trim($line); + if($line === ''){ + continue; + } + $parsed = wp_parse_url($line); + if(isset($parsed['scheme']) && isset($parsed['host'])){ + $origin = $parsed['scheme'] . '://' . $parsed['host']; + if(isset($parsed['port'])){ + $origin .= ':' . $parsed['port']; + } + $origins[] = $origin; + } + } + return $origins; } // Get user by username or email -function wwa_get_user($username) { +function wwa_get_user($username){ if(wwa_get_option('email_login') !== 'true'){ return get_user_by('login', $username); }else{ @@ -400,3 +457,54 @@ function wwa_get_user($username) { return get_user_by('login', $username); } } + +// Provide plugin version for other plugins +function wwa_loaded_version(){ + if(!get_option('wwa_version')){ + return '0.0.1'; + } + return get_option('wwa_version')['version']; +} + +// Register query vars +function wwa_query_vars($vars) { + $vars[] = 'wwa-well-known-ror'; + return $vars; +} + +// Add rewrite rules for .well-known/webauthn +function wwa_add_rewrite_rules() { + add_rewrite_rule('^\.well-known/webauthn$', 'index.php?wwa-well-known-ror=true', 'top'); +} +function wwa_apply_rewrite_rules() { + wwa_add_rewrite_rules(); + flush_rewrite_rules(); +} + +// Handle .well-known/webauthn +function wwa_handle_ror($wp) { + if (array_key_exists('wwa-well-known-ror', $wp->query_vars)) { + header('Content-Type: application/json'); + header('Access-Control-Allow-Origin: *'); + echo wp_json_encode(array( + 'origins'=> wwa_get_ror_list() + )); + exit; + } +} + +// Initialize plugin data for a new site created in a multisite network +function wwa_new_site_init($new_site){ + $network_active = get_site_option('active_sitewide_plugins'); + if(isset($network_active['wp-webauthn/wp-webauthn.php'])){ + switch_to_blog($new_site->id); + wwa_init_data(); + wwa_apply_rewrite_rules(); + restore_current_blog(); + } +} +add_action('wp_initialize_site', 'wwa_new_site_init'); + +add_filter('query_vars', 'wwa_query_vars'); +add_action('parse_request', 'wwa_handle_ror', 99); +add_action('init', 'wwa_add_rewrite_rules', 1); diff --git a/wp-content/plugins/wp-webauthn/wwa-menus.php b/wp-content/plugins/wp-webauthn/wwa-menus.php index a3a81737..d8cbac30 100644 --- a/wp-content/plugins/wp-webauthn/wwa-menus.php +++ b/wp-content/plugins/wp-webauthn/wwa-menus.php @@ -1,7 +1,11 @@ "'.$post_first_choice.'"'); + } + wwa_update_option('first_choice', $post_first_choice); + + $post_user_verification = sanitize_text_field(wp_unslash($_POST['user_verification'] ?? 'false')); + if(!in_array($post_user_verification, array('true', 'false'), true)){ + $post_user_verification = 'false'; + } + if($post_user_verification !== wwa_get_option('user_verification')){ + wwa_add_log($res_id, 'network user_verification: "'.wwa_get_option('user_verification').'"->"'.$post_user_verification.'"'); + } + wwa_update_option('user_verification', $post_user_verification); + + $post_usernameless_login = sanitize_text_field(wp_unslash($_POST['usernameless_login'] ?? 'false')); + if(!in_array($post_usernameless_login, array('true', 'false'), true)){ + $post_usernameless_login = 'false'; + } + if($post_usernameless_login !== wwa_get_option('usernameless_login')){ + wwa_add_log($res_id, 'network usernameless_login: "'.wwa_get_option('usernameless_login').'"->"'.$post_usernameless_login.'"'); + } + wwa_update_option('usernameless_login', $post_usernameless_login); + + $post_allow_type = sanitize_text_field(wp_unslash($_POST['allow_authenticator_type'] ?? 'none')); + if(!in_array($post_allow_type, array('none', 'platform', 'cross-platform'), true)){ + $post_allow_type = 'none'; + } + if($post_allow_type !== wwa_get_option('allow_authenticator_type')){ + wwa_add_log($res_id, 'network allow_authenticator_type: "'.wwa_get_option('allow_authenticator_type').'"->"'.$post_allow_type.'"'); + } + wwa_update_option('allow_authenticator_type', $post_allow_type); + + $post_show_type = sanitize_text_field(wp_unslash($_POST['show_authenticator_type'] ?? 'false')); + if(!in_array($post_show_type, array('true', 'false'), true)){ + $post_show_type = 'false'; + } + if($post_show_type !== wwa_get_option('show_authenticator_type')){ + wwa_add_log($res_id, 'network show_authenticator_type: "'.wwa_get_option('show_authenticator_type').'"->"'.$post_show_type.'"'); + } + wwa_update_option('show_authenticator_type', $post_show_type); + + $raw_ror = wp_unslash($_POST['ror_origins'] ?? ''); + $ror_lines = explode("\n", $raw_ror); + $sanitized_ror = array(); + foreach($ror_lines as $line){ + $line = trim($line); + if($line === ''){ + continue; + } + $parsed = wp_parse_url($line); + if(isset($parsed['scheme']) && isset($parsed['host'])){ + $origin = $parsed['scheme'] . '://' . $parsed['host']; + if(isset($parsed['port'])){ + $origin .= ':' . $parsed['port']; + } + $sanitized_ror[] = $origin; + } + } + $post_ror_origins = implode("\n", $sanitized_ror); + if($post_ror_origins !== wwa_get_option('ror_origins')){ + wwa_add_log($res_id, 'network ror_origins: "'.str_replace("\n", ', ', wwa_get_option('ror_origins')).'"->"'.str_replace("\n", ', ', $post_ror_origins).'"'); + } + wwa_update_option('ror_origins', $post_ror_origins); + + wp_safe_redirect(add_query_arg('updated', 'true', network_admin_url('settings.php?page=wwa_network_admin'))); + exit; +} + +// Display network settings page +function wwa_display_network_settings(){ + if(!current_user_can('manage_network_options')){ + wp_die(esc_html__('You do not have sufficient permissions to access this page.')); + } + + wp_enqueue_script('wwa_admin', plugins_url('js/admin.js', __FILE__), array(), get_option('wwa_version')['version']); + wp_localize_script('wwa_admin', 'php_vars', array( + 'ajax_url' => admin_url('admin-ajax.php'), + '_ajax_nonce' => wp_create_nonce('wwa_admin_ajax'), + 'i18n_1' => __('User verification is disabled by default because some mobile devices do not support it (especially on Android devices). But we recommend you to enable it if possible to further secure your login.', 'wp-webauthn'), + 'i18n_2' => __('Log count: ', 'wp-webauthn'), + 'i18n_3' => __('Loading failed, maybe try refreshing?', 'wp-webauthn') + )); + wp_enqueue_style('wwa_admin', plugins_url('css/admin.css', __FILE__)); + + $wwa_not_allowed = false; + if(!function_exists('mb_substr') || !function_exists('gmp_intval') || !function_exists('sodium_crypto_sign_detached') || !wwa_check_ssl() && (wp_parse_url(site_url(), PHP_URL_HOST) !== 'localhost' && wp_parse_url(site_url(), PHP_URL_HOST) !== '127.0.0.1')){ + $wwa_not_allowed = true; + } + ?> + +
+

WP-WebAuthn

+ + +

+ + +

+ +
+ +
-

User that doesn\'t have any registered authenticator (e.g. new user) will unable to login when using "WebAuthn Only".
When the browser does not support WebAuthn, the login method will default to password if password login is not disabled.', 'wp-webauthn');?>

+

User that doesn\'t have any registered authenticator (e.g. new user) will unable to login when using "WebAuthn Only".
When the browser does not support WebAuthn, the login method will default to password if password login is not disabled.', 'wp-webauthn'), array('br' => array()));?>

- -

DOES NOT affect the authentication process in anyway.', 'wp-webauthn');?>

-
- -

MUST be exactly the same with the current domain or parent domain.', 'wp-webauthn');?>

+ +
+
+
+

Passkey is the brand name for this new way of digital authentication, while WebAuthn is the name of the technical standard under the hood.', 'wp-webauthn'), array('br' => array()));?>

+
+ +

DOES NOT affect the authentication process in anyway.', 'wp-webauthn'), array('strong' => array()));?>

+
+ +

MUST be exactly the same with the current domain or parent domain.', 'wp-webauthn'), array('strong' => array()));?>

+
-
-
-

+
+
+

-
-
-

Note that if enabled attackers may be able to brute force the correspondences between email addresses and users.', 'wp-webauthn');?>

+
+
+

Note that if enabled attackers may be able to brute force the correspondences between email addresses and users.', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?>

-
-
-

If you cannot register or verify your authenticators, please consider disabling user verification.', 'wp-webauthn');?>

+
+
+

If you cannot register or verify your authenticators, please consider disabling user verification.', 'wp-webauthn'), array('br' => array()));?>

-
-
-

User verification will be enabled automatically when authenticating with usernameless authentication feature.
Some authenticators and some browsers DO NOT support this feature.', 'wp-webauthn');?>

+
+
+

User verification will be enabled automatically when authenticating with usernameless authentication feature.
Some authenticators and some browsers DO NOT support this feature.', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?>

-

+

+ +
+
+
+

The "Allow a specific type" restriction above still applies regardless of this setting.', 'wp-webauthn'), array('br' => array()));?>

+
+
-

If "Everyone except administrators" is selected, only administrators with the "Edit user" permission will be able to update passwords (for all users).', 'wp-webauthn');?>

+

If "Everyone except administrators" is selected, only administrators with the "Edit user" permission will be able to update passwords (for all users).', 'wp-webauthn'), array('br' => array()));?>

-

By default, new users have to login manually after registration. If "WebAuthn Only" is enabled, they will not be able to login.
When using "Log user in", new users will be logged in automatically and redirected to their profile settings so that they can set up WebAuthn authenticators.', 'wp-webauthn');?>

+

By default, new users have to login manually after registration. If "WebAuthn Only" is enabled, they will not be able to login.
When using "Log user in", new users will be logged in automatically and redirected to their profile settings so that they can set up WebAuthn authenticators.', 'wp-webauthn'), array('br' => array()));?> +When using "Send login link", an one-time login link will be automatically sent to the user\'s email adress. This will replace the default WordPress welcome email.
"Send login link" will work even if "Allow user login by login link via email" is disabled.', 'wp-webauthn'), array('br' => array(), 'strong' => array())); +} +?> +

-
-
+
+

-       +      

-

Note: Logs may contain sensitive information.', 'wp-webauthn');?>

+

Note: Logs may contain sensitive information.', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?>

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + admin_url('admin-ajax.php'), + '_ajax_nonce' => wp_create_nonce('wwa_ajax'), 'user_id' => $user->ID, 'i18n_1' => __('Initializing...', 'wp-webauthn'), 'i18n_2' => __('Please follow instructions to finish registration...', 'wp-webauthn'), @@ -19,7 +26,7 @@ wp_localize_script('wwa_profile', 'php_vars', array( 'i18n_13' => __('Please follow instructions to finish verification...', 'wp-webauthn'), 'i18n_14' => __('Verifying...', 'wp-webauthn'), 'i18n_15' => ''.__('Verification failed', 'wp-webauthn').'', - 'i18n_16' => ''.__('Verification passed! You can now log in through WebAuthn', 'wp-webauthn').'', + 'i18n_16' => ''.(wwa_get_option('terminology') === 'webauthn' ? __('Verification passed! You can now log in through WebAuthn', 'wp-webauthn') : __('Verification passed! You can now log in with this passkey', 'wp-webauthn')).'', 'i18n_17' => __('No registered authenticators', 'wp-webauthn'), 'i18n_18' => __('Confirm removal of authenticator: ', 'wp-webauthn'), 'i18n_19' => __('Removing...', 'wp-webauthn'), @@ -30,38 +37,40 @@ wp_localize_script('wwa_profile', 'php_vars', array( 'i18n_25' => __('No', 'wp-webauthn'), 'i18n_26' => __(' (Unavailable)', 'wp-webauthn'), 'i18n_27' => __('The site administrator has disabled usernameless login feature.', 'wp-webauthn'), - 'i18n_28' => __('After removing this authenticator, you will not be able to login with WebAuthn', 'wp-webauthn'), + // translators: %s: 'WebAuthn' or 'passkey' + 'i18n_28' => sprintf(__('After removing this authenticator, you will not be able to login with %s', 'wp-webauthn'), $wwa_term ? 'WebAuthn' : __('Passkey', 'wp-webauthn')), 'i18n_29' => __(' (Disabled)', 'wp-webauthn'), 'i18n_30' => __('The site administrator only allow platform authenticators currently.', 'wp-webauthn'), 'i18n_31' => __('The site administrator only allow roaming authenticators currently.', 'wp-webauthn') )); wp_enqueue_style('wwa_profile', plugins_url('css/admin.css', __FILE__)); -wp_localize_script('wwa_profile', 'configs', array('usernameless' => (wwa_get_option('usernameless_login') === false ? "false" : wwa_get_option('usernameless_login')), 'allow_authenticator_type' => (wwa_get_option('allow_authenticator_type') === false ? "none" : wwa_get_option('allow_authenticator_type')))); +wp_localize_script('wwa_profile', 'configs', array( + 'usernameless' => (wwa_get_option('usernameless_login') === false ? "false" : wwa_get_option('usernameless_login')), + 'allow_authenticator_type' => (wwa_get_option('allow_authenticator_type') === false ? "none" : wwa_get_option('allow_authenticator_type')), + 'show_authenticator_type' => (wwa_get_option('show_authenticator_type') === false ? "true" : wwa_get_option('show_authenticator_type')) +)); ?>
-

WebAuthn

+

WebAuthn

user_login])){ - $user_id = $user_ids[$user->user_login]; - $count = 0; - $data = json_decode(wwa_get_option("user_credentials_meta"), true); - foreach($data as $key => $value){ - if($user_id === $value["user"]){ - $count++; - break; - } - } - } + global $wpdb; + $count = intval($wpdb->get_var($wpdb->prepare( + "SELECT COUNT(*) FROM {$wpdb->wwa_credentials} WHERE user_id = %d AND registered_blog_id = %d", + $user->ID, get_current_blog_id() + ))); } if($count === 0){ ?>
- +
-


+



-

+

- - - - - - + + + + + + - + - - - - - - + + + + + +

-   +  
- -

- -

%s.
You can register multiple authenticators for an account.', 'wp-webauthn'), $user->user_login);?>

+ +

+ +

%2$s.
You can register multiple %3$s for an account.', 'wp-webauthn'), $wwa_term_singular, esc_html($user->user_login), $wwa_term_plural), array('strong' => array(), 'br' => array()));?>

+ - + + - + - +
- + + + -

Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn');?>

+

Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn'), array('br' => array()));?>

- -

DOES NOT affect the authentication process in anyway.', 'wp-webauthn');?>

+ +

DOES NOT affect the authentication process in anyway.', 'wp-webauthn'), array('strong' => array()));?>

-
-
-

Some authenticators like U2F-only authenticators and some browsers DO NOT support this feature.
A record will be stored in the authenticator permanently untill you reset it.', 'wp-webauthn');?>

+
+
+

Some authenticators like U2F-only authenticators and some browsers DO NOT support this feature.
A record will be stored in the authenticator permanently untill you reset it.', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?>

-      +     
- -

-

-      + +

+

+      -

      +

     
\ No newline at end of file diff --git a/wp-content/plugins/wp-webauthn/wwa-shortcodes.php b/wp-content/plugins/wp-webauthn/wwa-shortcodes.php index dd5fd7f8..2469c1c6 100644 --- a/wp-content/plugins/wp-webauthn/wwa-shortcodes.php +++ b/wp-content/plugins/wp-webauthn/wwa-shortcodes.php @@ -1,14 +1,22 @@ admin_url('admin-ajax.php'), + '_ajax_nonce' => wp_create_nonce('wwa_ajax'), 'admin_url' => admin_url(), 'usernameless' => (wwa_get_option('usernameless_login') === false ? "false" : wwa_get_option('usernameless_login')), 'remember_me' => (wwa_get_option('remember_me') === false ? "false" : wwa_get_option('remember_me')), 'allow_authenticator_type' => (wwa_get_option('allow_authenticator_type') === false ? "none" : wwa_get_option('allow_authenticator_type')), + 'show_authenticator_type' => (wwa_get_option('show_authenticator_type') === false ? "true" : wwa_get_option('show_authenticator_type')), + 'terminology' => (wwa_get_option('terminology') === false ? 'passkey' : wwa_get_option('terminology')), 'i18n_1' => __('Ready', 'wp-webauthn'), - 'i18n_2' => __('Authenticate with WebAuthn', 'wp-webauthn'), + 'i18n_2' => wwa_get_option('terminology') === 'webauthn' ? __('Authenticate with WebAuthn', 'wp-webauthn') : __('Authenticate with a passkey', 'wp-webauthn'), 'i18n_3' => __('Hold on...', 'wp-webauthn'), 'i18n_4' => __('Please proceed...', 'wp-webauthn'), 'i18n_5' => __('Authenticating...', 'wp-webauthn'), @@ -49,16 +57,16 @@ function wwa_localize_frontend(){ // Login form function wwa_login_form_shortcode($vals){ - extract(shortcode_atts( + $atts = shortcode_atts( array( 'traditional' => 'true', 'username' => '', 'auto_hide' => 'true', 'to' => '' - ), $vals) + ), $vals ); - if($auto_hide === "true" && current_user_can("read")){ + if($atts['auto_hide'] === "true" && current_user_can("read")){ return ''; } @@ -70,24 +78,24 @@ function wwa_login_form_shortcode($vals){ $html_form = ''; @@ -98,15 +106,15 @@ add_shortcode('wwa_login_form', 'wwa_login_form_shortcode'); // Register form function wwa_register_form_shortcode($vals){ - extract(shortcode_atts( + $atts = shortcode_atts( array( 'display' => 'true' - ), $vals) + ), $vals ); // If always display if(!current_user_can("read")){ - if($display === "true"){ + if($atts['display'] === "true"){ return '

'.__('You haven\'t logged in yet.', 'wp-webauthn').'

'; }else{ return ''; @@ -120,15 +128,17 @@ function wwa_register_form_shortcode($vals){ wp_enqueue_style('wwa_frondend_css', plugins_url('css/frontend.css', __FILE__), array(), get_option('wwa_version')['version']); $allowed_type = wwa_get_option('allow_authenticator_type') === false ? 'none' : wwa_get_option('allow_authenticator_type'); - return ' -
+ $show_type = wwa_get_option('show_authenticator_type') !== 'false'; + $type_selector = $show_type ? ' -

'.__('If a type is selected, the browser will only prompt for authenticators of selected type.
Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn').'

+

'.__('If a type is selected, the browser will only prompt for authenticators of selected type.
Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn').'

' : ''; + return ' +
'.$type_selector.'

'.__('An easily identifiable name for the authenticator. DOES NOT affect the authentication process in anyway.', 'wp-webauthn').'

'.( @@ -140,15 +150,15 @@ add_shortcode('wwa_register_form', 'wwa_register_form_shortcode'); // Verify button function wwa_verify_button_shortcode($vals){ - extract(shortcode_atts( + $atts = shortcode_atts( array( 'display' => 'true' - ), $vals) + ), $vals ); // If always display if(!current_user_can("read")){ - if($display === "true"){ + if($atts['display'] === "true"){ return '

'.__('You haven\'t logged in yet.', 'wp-webauthn').'

'; }else{ return ''; @@ -166,23 +176,26 @@ add_shortcode('wwa_verify_button', 'wwa_verify_button_shortcode'); // Authenticator list function wwa_list_shortcode($vals){ - extract(shortcode_atts( + $atts = shortcode_atts( array( 'display' => 'true' - ), $vals) + ), $vals ); - $thead = '
'; - $tbody = ''; - $tfoot = '
'.__('Identifier', 'wp-webauthn').''.__('Type', 'wp-webauthn').''._x('Registered', 'time', 'wp-webauthn').''.__('Last used', 'wp-webauthn').''.__('Usernameless', 'wp-webauthn').''.__('Action', 'wp-webauthn').'
'.__('Loading...', 'wp-webauthn').'
'.__('Identifier', 'wp-webauthn').''.__('Type', 'wp-webauthn').''._x('Registered', 'time', 'wp-webauthn').''.__('Last used', 'wp-webauthn').''.__('Usernameless', 'wp-webauthn').''.__('Action', 'wp-webauthn').'

'; + $show_type = wwa_get_option('show_authenticator_type') !== 'false'; + $type_th = $show_type ? ''.__('Type', 'wp-webauthn').'' : ''; + $loading_colspan = $show_type ? '5' : '4'; + $thead = '
'.$type_th.''; + $tbody = ''; + $tfoot = ''.$type_th.'
'.__('Identifier', 'wp-webauthn').''._x('Registered', 'time', 'wp-webauthn').''.__('Last used', 'wp-webauthn').''.__('Usernameless', 'wp-webauthn').''.__('Action', 'wp-webauthn').'
'.__('Loading...', 'wp-webauthn').'
'.__('Identifier', 'wp-webauthn').''._x('Registered', 'time', 'wp-webauthn').''.__('Last used', 'wp-webauthn').''.__('Usernameless', 'wp-webauthn').''.__('Action', 'wp-webauthn').'

'; // If always display if(!current_user_can("read")){ - if($display === "true"){ + if($atts['display'] === "true"){ // Load CSS wp_enqueue_style('wwa_frondend_css', plugins_url('css/frontend.css', __FILE__), array(), get_option('wwa_version')['version']); - return $thead.''.__('You haven\'t logged in yet.', 'wp-webauthn').''.$tfoot; + return $thead.''.__('You haven\'t logged in yet.', 'wp-webauthn').''.$tfoot; }else{ return ''; } diff --git a/wp-content/plugins/wp-webauthn/wwa-version.php b/wp-content/plugins/wp-webauthn/wwa-version.php index 1c9f67c1..5039f821 100644 --- a/wp-content/plugins/wp-webauthn/wwa-version.php +++ b/wp-content/plugins/wp-webauthn/wwa-version.php @@ -1,5 +1,9 @@ '1.3.4', - 'commit' => 'b7ef5ce' + 'version' => '1.4.1', + 'commit' => 'f5955ea' );