updated plugin WP-WebAuthn version 1.4.1
This commit is contained in:
45
wp-content/plugins/wp-webauthn/changelog.txt
Normal file
45
wp-content/plugins/wp-webauthn/changelog.txt
Normal file
@ -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
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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();
|
||||
|
||||
@ -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 = `<span id="wwa-username-label">${php_vars.email_login === 'true' ? php_vars.i18n_10 : php_vars.i18n_9}</span>${dom[0].innerHTML.split('<br>')[1]}`;
|
||||
dom[0].innerHTML = `<span id="wwa-username-label">${wwa_login_php_vars.email_login === 'true' ? wwa_login_php_vars.i18n_10 : wwa_login_php_vars.i18n_9}</span>${dom[0].innerHTML.split('<br>')[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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = `<tr><td colspan="${document.getElementsByClassName('wwa-usernameless-th')[0].style.display === 'none' ? '5' : '6'}">${wwa_php_vars.i18n_17}</td></tr>` }, 'class');
|
||||
wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `<tr><td colspan="${getFrontendColspan()}">${wwa_php_vars.i18n_17}</td></tr>` }, '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 = `<tr><td colspan="${document.getElementsByClassName('wwa-usernameless-th')[0].style.display === 'none' ? '5' : '6'}">${wwa_php_vars.i18n_23}</td></tr>` }, 'class');
|
||||
wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `<tr><td colspan="${getFrontendColspan()}">${wwa_php_vars.i18n_23}</td></tr>` }, '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 += `<tr><td>${item.name}</td><td>${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 : ''}</td><td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? wwa_php_vars.i18n_1 + (wwa_php_vars.usernameless === 'true' ? '' : wwa_php_vars.i18n_9) : wwa_php_vars.i18n_8}</td><td class="wwa-key-${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${wwa_php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${wwa_php_vars.i18n_27}</a></td></tr>`;
|
||||
htmlStr += `<tr><td>${item.name}</td>${wwa_php_vars.show_authenticator_type === 'true' ? `<td class="wwa-type-td">${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 : ''}</td>` : ''}<td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? wwa_php_vars.i18n_1 + (wwa_php_vars.usernameless === 'true' ? '' : wwa_php_vars.i18n_9) : wwa_php_vars.i18n_8}</td><td class="wwa-key-${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${wwa_php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${wwa_php_vars.i18n_27}</a></td></tr>`;
|
||||
}
|
||||
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 = `<tr><td colspan="${document.getElementsByClassName('wwa-usernameless-th')[0].style.display === 'none' ? '5' : '6'}">${wwa_php_vars.i18n_17}</td></tr>` }, 'class');
|
||||
wwa_dom('wwa-authenticator-list', (dom) => { dom.innerHTML = `<tr><td colspan="${getFrontendColspan()}">${wwa_php_vars.i18n_17}</td></tr>` }, '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 {
|
||||
|
||||
@ -1,5 +1,8 @@
|
||||
'use strict';
|
||||
|
||||
const wwa_passkey_notice_svg = '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 216 216;width:28px" viewBox="0 0 216 216"><style>.st2{fill-rule:evenodd;clip-rule:evenodd}</style><g id="Isolation_Mode"><path d="M0 0h216v216H0z" style="fill:none"/><path d="M172.32 96.79c0 13.78-8.48 25.5-20.29 29.78l7.14 11.83-10.57 13 10.57 12.71-17.04 22.87-12.01-12.82V125.7c-10.68-4.85-18.15-15.97-18.15-28.91 0-17.4 13.51-31.51 30.18-31.51 16.66 0 30.17 14.11 30.17 31.51zm-30.18 4.82c4.02 0 7.28-3.4 7.28-7.6 0-4.2-3.26-7.61-7.28-7.61s-7.28 3.4-7.28 7.61c-.01 4.2 3.26 7.6 7.28 7.6z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#353535"/><path d="M172.41 96.88c0 13.62-8.25 25.23-19.83 29.67l6.58 11.84-9.73 13 9.73 12.71-17.03 23.05v-85.54c4.02 0 7.28-3.41 7.28-7.6 0-4.2-3.26-7.61-7.28-7.61V65.28c16.73 0 30.28 14.15 30.28 31.6zM120.24 131.43c-9.75-8-16.3-20.3-17.2-34.27H50.8c-10.96 0-19.84 9.01-19.84 20.13v25.17c0 5.56 4.44 10.07 9.92 10.07h69.44c5.48 0 9.92-4.51 9.92-10.07v-11.03z" class="st2"/><path d="M73.16 91.13c-2.42-.46-4.82-.89-7.11-1.86-8.65-3.63-13.69-10.32-15.32-19.77-1.12-6.47-.59-12.87 2.03-18.92 3.72-8.6 10.39-13.26 19.15-14.84 5.24-.94 10.46-.73 15.5 1.15 7.59 2.82 12.68 8.26 15.03 16.24 2.38 8.05 2.03 16.1-1.56 23.72-3.72 7.96-10.21 12.23-18.42 13.9-.68.14-1.37.27-2.05.41-2.41-.03-4.83-.03-7.25-.03z" style="fill:#141313"/></g></svg>';
|
||||
const wwa_passkey_btn_svg = '<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 216 216;width:28px" viewBox="0 0 216 216"><style>.st2{fill-rule:evenodd;clip-rule:evenodd}</style><g id="Isolation_Mode"><path d="M0 0h216v216H0z" style="fill:none"/><path d="M172.32 96.79c0 13.78-8.48 25.5-20.29 29.78l7.14 11.83-10.57 13 10.57 12.71-17.04 22.87-12.01-12.82V125.7c-10.68-4.85-18.15-15.97-18.15-28.91 0-17.4 13.51-31.51 30.18-31.51 16.66 0 30.17 14.11 30.17 31.51zm-30.18 4.82c4.02 0 7.28-3.4 7.28-7.6 0-4.2-3.26-7.61-7.28-7.61s-7.28 3.4-7.28 7.61c-.01 4.2 3.26 7.6 7.28 7.6z" style="fill-rule:evenodd;clip-rule:evenodd;fill:currentcolor"/><path d="M172.41 96.88c0 13.62-8.25 25.23-19.83 29.67l6.58 11.84-9.73 13 9.73 12.71-17.03 23.05v-85.54c4.02 0 7.28-3.41 7.28-7.6 0-4.2-3.26-7.61-7.28-7.61V65.28c16.73 0 30.28 14.15 30.28 31.6zM120.24 131.43c-9.75-8-16.3-20.3-17.2-34.27H50.8c-10.96 0-19.84 9.01-19.84 20.13v25.17c0 5.56 4.44 10.07 9.92 10.07h69.44c5.48 0 9.92-4.51 9.92-10.07v-11.03z" class="st2" style="fill:currentcolor"/><path d="M73.16 91.13c-2.42-.46-4.82-.89-7.11-1.86-8.65-3.63-13.69-10.32-15.32-19.77-1.12-6.47-.59-12.87 2.03-18.92 3.72-8.6 10.39-13.26 19.15-14.84 5.24-.94 10.46-.73 15.5 1.15 7.59 2.82 12.68 8.26 15.03 16.24 2.38 8.05 2.03 16.1-1.56 23.72-3.72 7.96-10.21 12.23-18.42 13.9-.68.14-1.37.27-2.05.41-2.41-.03-4.83-.03-7.25-.03z" style="fill:currentcolor"/></g></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'
|
||||
? `<span class="wwa-passkey-notice">${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}</span>`
|
||||
: `<span><span class="dashicons dashicons-shield-alt"></span> ${wwa_login_php_vars.i18n_2}</span>`;
|
||||
}, '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 = '<span class="dashicons dashicons-update-alt"></span>';
|
||||
button_toggle.innerHTML = '<span class="dashicons dashicons-ellipsis password-icon"></span>';
|
||||
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 = `<span><span class="dashicons dashicons-shield-alt"></span> ${php_vars.i18n_2}<span>`;
|
||||
notice.innerHTML = wwa_login_php_vars.terminology ==='passkey' ? `<span class="wwa-passkey-notice">${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}</span>` : `<span><span class="dashicons dashicons-shield-alt"></span> ${wwa_login_php_vars.i18n_2}</span>`;
|
||||
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 = `<span><span class="dashicons dashicons-shield-alt"></span> ${php_vars.i18n_2}</span>` }, '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' ? `<span class="wwa-passkey-notice">${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}</span>` : `<span><span class="dashicons dashicons-shield-alt"></span> ${wwa_login_php_vars.i18n_2}</span>` }, '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 = '<span class="dashicons dashicons-shield-alt"></span>';
|
||||
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 = `<span><span class="dashicons dashicons-shield-alt"></span> ${php_vars.i18n_2}</span>` }, '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' ? `<span class="wwa-passkey-notice">${wwa_passkey_notice_svg} ${wwa_login_php_vars.i18n_2}</span>` : `<span><span class="dashicons dashicons-shield-alt"></span> ${wwa_login_php_vars.i18n_2}</span>` }, 'class');
|
||||
wwa_dom('wp-submit', (dom) => { dom.disabled = true }, 'id');
|
||||
wwa_dom('wp-webauthn', (dom) => {
|
||||
dom.innerHTML = '<span class="dashicons dashicons-ellipsis password-icon"></span>';
|
||||
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 });
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
const svg = `<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" style="enable-background:new 0 0 216 216" viewBox="0 0 216 216" class="wwa-table-svg"><style>.st2{fill-rule:evenodd;clip-rule:evenodd;fill:#818286}</style><g id="Isolation_Mode"><path d="M0 0h216v216H0z" style="fill:none"/><path d="M172.32 96.79c0 13.78-8.48 25.5-20.29 29.78l7.14 11.83-10.57 13 10.57 12.71-17.04 22.87-12.01-12.82V125.7c-10.68-4.85-18.15-15.97-18.15-28.91 0-17.4 13.51-31.51 30.18-31.51 16.66 0 30.17 14.11 30.17 31.51zm-30.18 4.82c4.02 0 7.28-3.4 7.28-7.6 0-4.2-3.26-7.61-7.28-7.61s-7.28 3.4-7.28 7.61c-.01 4.2 3.26 7.6 7.28 7.6z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#a2a1a3"/><path d="M172.41 96.88c0 13.62-8.25 25.23-19.83 29.67l6.58 11.84-9.73 13 9.73 12.71-17.03 23.05v-85.54c4.02 0 7.28-3.41 7.28-7.6 0-4.2-3.26-7.61-7.28-7.61V65.28c16.73 0 30.28 14.15 30.28 31.6zM120.24 131.43c-9.75-8-16.3-20.3-17.2-34.27H50.8c-10.96 0-19.84 9.01-19.84 20.13v25.17c0 5.56 4.44 10.07 9.92 10.07h69.44c5.48 0 9.92-4.51 9.92-10.07v-11.03z" class="st2"/><path d="M73.16 91.13c-2.42-.46-4.82-.89-7.11-1.86-8.65-3.63-13.69-10.32-15.32-19.77-1.12-6.47-.59-12.87 2.03-18.92 3.72-8.6 10.39-13.26 19.15-14.84 5.24-.94 10.46-.73 15.5 1.15 7.59 2.82 12.68 8.26 15.03 16.24 2.38 8.05 2.03 16.1-1.56 23.72-3.72 7.96-10.21 12.23-18.42 13.9-.68.14-1.37.27-2.05.41-2.41-.03-4.83-.03-7.25-.03z" style="fill:#818286"/></g></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(`<tr><td colspan="${jQuery('.wwa-usernameless-th').css('display') === 'none' ? '5' : '6'}">${php_vars.i18n_8}</td></tr>`);
|
||||
jQuery('#wwa-authenticator-list').html(`<tr><td colspan="${getColspan()}">${php_vars.i18n_8}</td></tr>`);
|
||||
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(`<tr><td colspan="${jQuery('.wwa-usernameless-th').css('display') === 'none' ? '5' : '6'}">${php_vars.i18n_17}</td></tr>`);
|
||||
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(`<tr><td colspan="${getColspan()}">${php_vars.i18n_17}</td></tr>`);
|
||||
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 += `<tr><td>${item.name}</td><td>${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 : ''}</td><td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? php_vars.i18n_24 + (configs.usernameless === 'true' ? '' : php_vars.i18n_26) : php_vars.i18n_25}</td><td id="${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${php_vars.i18n_12}</a></td></tr>`;
|
||||
htmlStr += `<tr><td>${svg}${item.name}</td>${configs.show_authenticator_type === 'true' ? `<td class="wwa-type-td">${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 : ''}</td>` : ''}<td>${item.added}</td><td>${item.last_used}</td><td class="wwa-usernameless-td">${item.usernameless ? php_vars.i18n_24 + (configs.usernameless === 'true' ? '' : php_vars.i18n_26) : php_vars.i18n_25}</td><td id="${item.key}"><a href="javascript:renameAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${php_vars.i18n_20}</a> | <a href="javascript:removeAuthenticator('${item.key}', '${item.name.replaceAll('\'', '\\\'').replaceAll(''', '\\'').replaceAll('"', '\\"')}')">${php_vars.i18n_12}</a></td></tr>`;
|
||||
}
|
||||
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(`<tr><td colspan="${jQuery('.wwa-usernameless-th').css('display') === 'none' ? '5' : '6'}">${php_vars.i18n_8}</td></tr>`);
|
||||
jQuery('#wwa-authenticator-list').html(`<tr><td colspan="${getColspan()}">${php_vars.i18n_8}</td></tr>`);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 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();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -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
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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"
|
||||
@ -23,7 +23,7 @@
|
||||
"sort-packages": true
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.0 || ^8.0",
|
||||
"php": "^7.1 || ^8.0",
|
||||
"ext-simplexml": "*",
|
||||
"ext-mbstring": "*",
|
||||
"ext-ctype": "*",
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|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<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $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;
|
||||
}
|
||||
|
||||
|
||||
@ -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'),
|
||||
|
||||
@ -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',
|
||||
),
|
||||
|
||||
@ -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": [
|
||||
{
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
<?php return array(
|
||||
'root' => 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(),
|
||||
|
||||
@ -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<static>
|
||||
*/
|
||||
public static function tokenize(string $code, int $flags = 0): array
|
||||
{
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.1"
|
||||
"php": ">=7.2"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Polyfill\\Php81\\": "" },
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,13 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Insert CSS and JS
|
||||
wp_enqueue_script('wwa_admin', plugins_url('js/admin.js', __FILE__));
|
||||
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 <strong>recommend you to enable it</strong> 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 <code>localhost</code>.', '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 <code>localhost</code>.', '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'))));
|
||||
?>
|
||||
<?php if(is_multisite() && wwa_validate_privileges()){ ?>
|
||||
<div class="notice notice-info">
|
||||
<p><?php
|
||||
/* translators: %1$s: opening link tag, %2$s: closing link tag */
|
||||
echo wp_kses(
|
||||
sprintf(
|
||||
__('Some settings are managed at the network level by the super administrator. %1$sConfigure network settings%2$s', 'wp-webauthn'),
|
||||
'<a href="' . esc_url(network_admin_url('settings.php?page=wwa_network_admin')) . '">',
|
||||
'</a>'
|
||||
),
|
||||
array('a' => array('href' => array()))
|
||||
);
|
||||
?></p>
|
||||
</div>
|
||||
<?php }
|
||||
|
||||
// Only admin can change settings
|
||||
if(wwa_validate_privileges()){ ?>
|
||||
@ -144,37 +213,73 @@ wp_nonce_field('wwa_options_update');
|
||||
?>
|
||||
<input type="hidden" name="wwa_ref" value="true">
|
||||
<table class="form-table">
|
||||
<?php if(!is_multisite()){ ?>
|
||||
<tr>
|
||||
<th scope="row"><label for="first_choice"><?php _e('Preferred login method', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="first_choice"><?php esc_html_e('Preferred login method', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_first_choice=wwa_get_option('first_choice');?>
|
||||
<select name="first_choice" id="first_choice">
|
||||
<option value="true"<?php if($wwa_v_first_choice === 'true' || !$wwa_not_allowed){?> selected<?php }?>><?php _e('Prefer WebAuthn', 'wp-webauthn');?></option>
|
||||
<option value="false"<?php if($wwa_v_first_choice === 'false'){?> selected<?php }?>><?php _e('Prefer password', 'wp-webauthn');?></option>
|
||||
<option value="webauthn"<?php if($wwa_v_first_choice === 'webauthn' && !$wwa_not_allowed){?> selected<?php }if($wwa_not_allowed){?> disabled<?php }?>><?php _e('WebAuthn Only', 'wp-webauthn');?></option>
|
||||
<option value="true"<?php if($wwa_v_first_choice !== 'false' && !($wwa_v_first_choice === 'webauthn' && !$wwa_not_allowed)){?> selected<?php }?>><?php esc_html_e('Prefer WebAuthn', 'wp-webauthn');?></option>
|
||||
<option value="false"<?php if($wwa_v_first_choice === 'false'){?> selected<?php }?>><?php esc_html_e('Prefer password', 'wp-webauthn');?></option>
|
||||
<option value="webauthn"<?php if($wwa_v_first_choice === 'webauthn' && !$wwa_not_allowed){?> selected<?php }if($wwa_not_allowed){?> disabled<?php }?>><?php esc_html_e('WebAuthn Only', 'wp-webauthn');?></option>
|
||||
</select>
|
||||
<p class="description"><?php _e('When using "WebAuthn Only", password login will be completely disabled. Please make sure your browser supports WebAuthn, otherwise you may unable to login.<br>User that doesn\'t have any registered authenticator (e.g. new user) will unable to login when using "WebAuthn Only".<br>When the browser does not support WebAuthn, the login method will default to password if password login is not disabled.', 'wp-webauthn');?></p>
|
||||
<p class="description"><?php echo wp_kses(__('When using "WebAuthn Only", password login will be completely disabled. Please make sure your browser supports WebAuthn, otherwise you may unable to login.<br>User that doesn\'t have any registered authenticator (e.g. new user) will unable to login when using "WebAuthn Only".<br>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()));?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<th scope="row"><label for="website_name"><?php _e('Website identifier', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="terminology"><?php esc_html_e('Terminology used for users', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<input required name="website_name" type="text" id="website_name" value="<?php echo wwa_get_option('website_name');?>" class="regular-text">
|
||||
<p class="description"><?php _e('This identifier is for identification purpose only and <strong>DOES NOT</strong> affect the authentication process in anyway.', 'wp-webauthn');?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="website_domain"><?php _e('Website domain', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<input required name="website_domain" type="text" id="website_domain" value="<?php echo wwa_get_option('website_domain');?>" class="regular-text">
|
||||
<p class="description"><?php _e('This field <strong>MUST</strong> be exactly the same with the current domain or parent domain.', 'wp-webauthn');?></p>
|
||||
<?php $wwa_v_t=wwa_get_option('terminology');
|
||||
if($wwa_v_t === false){
|
||||
wwa_update_option('terminology', 'webauthn');
|
||||
$wwa_v_t = 'webauthn';
|
||||
}
|
||||
?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="terminology" value="webauthn" <?php if($wwa_v_t === 'webauthn'){?>checked="checked"<?php }?>> WebAuthn</label><br>
|
||||
<label><input type="radio" name="terminology" value="passkey" <?php if($wwa_v_t === 'passkey'){?>checked="checked"<?php }?>> <?php echo esc_html_x('Passkey', 'Please note Passkey is a trademark owned by FIDO Alliance, please follow their guidelines for translation', 'wp-webauthn');?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('Choose how to name the authenticating technology to users.<br>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()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="remember_me"><?php _e('Allow to remember login', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="website_name"><?php esc_html_e('Website identifier', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<input required name="website_name" type="text" id="website_name" value="<?php echo esc_attr(wwa_get_option('website_name'));?>" class="regular-text">
|
||||
<p class="description"><?php echo wp_kses(__('This identifier is for identification purpose only and <strong>DOES NOT</strong> affect the authentication process in anyway.', 'wp-webauthn'), array('strong' => array()));?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="website_domain"><?php esc_html_e('Website domain', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<input required name="website_domain" type="text" id="website_domain" value="<?php echo esc_attr(wwa_get_option('website_domain'));?>" class="regular-text">
|
||||
<p class="description"><?php echo wp_kses(__('This field <strong>MUST</strong> be exactly the same with the current domain or parent domain.', 'wp-webauthn'), array('strong' => array()));?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if(!is_multisite()){ ?>
|
||||
<!-- Feature not fully ready <tr>
|
||||
<th scope="row"><label for="ror_origins"><?php esc_html_e('Related origins', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_ror = wwa_get_option('ror_origins');
|
||||
if($wwa_v_ror === false){
|
||||
wwa_update_option('ror_origins', '');
|
||||
$wwa_v_ror = '';
|
||||
}
|
||||
?>
|
||||
<textarea name="ror_origins" id="ror_origins" rows="4" cols="50" class="large-text code"><?php echo esc_textarea($wwa_v_ror);?></textarea>
|
||||
<p class="description"><?php echo wp_kses(__('Allow cross-site passkey usages (<a href="https://passkeys.dev/docs/advanced/related-origins/" target="_blank">Related Origin Requests</a>). May be useful for multi-site networks.<br> Enter one origin per line (e.g. <code>https://example.com</code>). Leave empty to disable.', 'wp-webauthn'), array('a' => array('href' => array(), 'target' => array()), 'br' => array(), 'code' => array()));?></p>
|
||||
</td>
|
||||
</tr> -->
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<th scope="row"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="remember_me"><?php esc_html_e('Allow to remember login', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_rm=wwa_get_option('remember_me');
|
||||
if($wwa_v_rm === false){
|
||||
@ -183,14 +288,14 @@ if($wwa_v_rm === false){
|
||||
}
|
||||
?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="remember_me" value="true" <?php if($wwa_v_rm === 'true'){?>checked="checked"<?php }?>> <?php _e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="remember_me" value="false" <?php if($wwa_v_rm === 'false'){?>checked="checked"<?php }?>> <?php _e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php _e('Show the \'Remember Me\' checkbox beside the login form when using WebAuthn.', 'wp-webauthn');?></p>
|
||||
<label><input type="radio" name="remember_me" value="true" <?php if($wwa_v_rm === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="remember_me" value="false" <?php if($wwa_v_rm === 'false'){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php esc_html_e('Show the \'Remember Me\' checkbox beside the login form when using WebAuthn.', 'wp-webauthn');?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="email_login"><?php _e('Allow to login with email addresses', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="email_login"><?php esc_html_e('Allow to login with email addresses', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_el=wwa_get_option('email_login');
|
||||
if($wwa_v_el === false){
|
||||
@ -199,25 +304,26 @@ if($wwa_v_el === false){
|
||||
}
|
||||
?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="email_login" value="true" <?php if($wwa_v_el === 'true'){?>checked="checked"<?php }?>> <?php _e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="email_login" value="false" <?php if($wwa_v_el === 'false'){?>checked="checked"<?php }?>> <?php _e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php _e('Allow to find users via email addresses when logging in.<br><strong>Note that if enabled attackers may be able to brute force the correspondences between email addresses and users.</strong>', 'wp-webauthn');?></p>
|
||||
<label><input type="radio" name="email_login" value="true" <?php if($wwa_v_el === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="email_login" value="false" <?php if($wwa_v_el === 'false'){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('Allow to find users via email addresses when logging in through WebAuthn.<br><strong>Note that if enabled attackers may be able to brute force the correspondences between email addresses and users.</strong>', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if(!is_multisite()){ ?>
|
||||
<tr>
|
||||
<th scope="row"><label for="user_verification"><?php _e('Require user verification', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="user_verification"><?php esc_html_e('Require user verification', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_uv=wwa_get_option('user_verification');?>
|
||||
<fieldset id="wwa-uv-field">
|
||||
<label><input type="radio" name="user_verification" value="true" <?php if($wwa_v_uv === 'true'){?>checked="checked"<?php }?>> <?php _e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="user_verification" value="false" <?php if($wwa_v_uv === 'false'){?>checked="checked"<?php }?>> <?php _e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php _e('User verification can improve security, but is not fully supported by mobile devices. <br> If you cannot register or verify your authenticators, please consider disabling user verification.', 'wp-webauthn');?></p>
|
||||
<label><input type="radio" name="user_verification" value="true" <?php if($wwa_v_uv === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="user_verification" value="false" <?php if($wwa_v_uv === 'false'){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('User verification can improve security, but is not fully supported by mobile devices. <br> If you cannot register or verify your authenticators, please consider disabling user verification.', 'wp-webauthn'), array('br' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="usernameless_login"><?php _e('Allow to login without username', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="usernameless_login"><?php esc_html_e('Allow to login without username', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_ul=wwa_get_option('usernameless_login');
|
||||
if($wwa_v_ul === false){
|
||||
@ -226,14 +332,14 @@ if($wwa_v_ul === false){
|
||||
}
|
||||
?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="usernameless_login" value="true" <?php if($wwa_v_ul === 'true'){?>checked="checked"<?php }?>> <?php _e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="usernameless_login" value="false" <?php if($wwa_v_ul === 'false'){?>checked="checked"<?php }?>> <?php _e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php _e('Allow users to register authenticator with usernameless authentication feature and login without username.<br><strong>User verification will be enabled automatically when authenticating with usernameless authentication feature.</strong><br>Some authenticators and some browsers <strong>DO NOT</strong> support this feature.', 'wp-webauthn');?></p>
|
||||
<label><input type="radio" name="usernameless_login" value="true" <?php if($wwa_v_ul === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="usernameless_login" value="false" <?php if($wwa_v_ul === 'false'){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('Allow users to register authenticator with usernameless authentication feature and login without username.<br><strong>User verification will be enabled automatically when authenticating with usernameless authentication feature.</strong><br>Some authenticators and some browsers <strong>DO NOT</strong> support this feature.', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="allow_authenticator_type"><?php _e('Allow a specific type of authenticator', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="allow_authenticator_type"><?php esc_html_e('Allow a specific type of authenticator', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_at=wwa_get_option('allow_authenticator_type');
|
||||
if($wwa_v_at === false){
|
||||
@ -242,18 +348,35 @@ if($wwa_v_at === false){
|
||||
}
|
||||
?>
|
||||
<select name="allow_authenticator_type" id="allow_authenticator_type">
|
||||
<option value="none"<?php if($wwa_v_at === 'none'){?> selected<?php }?>><?php _e('Any', 'wp-webauthn');?></option>
|
||||
<option value="platform"<?php if($wwa_v_at === 'platform'){?> selected<?php }?>><?php _e('Platform (e.g. Passkey or built-in sensors)', 'wp-webauthn');?></option>
|
||||
<option value="cross-platform"<?php if($wwa_v_at === 'cross-platform'){?> selected<?php }?>><?php _e('Roaming (e.g. USB security keys)', 'wp-webauthn');?></option>
|
||||
<option value="none"<?php if($wwa_v_at === 'none'){?> selected<?php }?>><?php esc_html_e('Any', 'wp-webauthn');?></option>
|
||||
<option value="platform"<?php if($wwa_v_at === 'platform'){?> selected<?php }?>><?php esc_html_e('Platform (e.g. Passkey or built-in sensors)', 'wp-webauthn');?></option>
|
||||
<option value="cross-platform"<?php if($wwa_v_at === 'cross-platform'){?> selected<?php }?>><?php esc_html_e('Roaming (e.g. USB security keys)', 'wp-webauthn');?></option>
|
||||
</select>
|
||||
<p class="description"><?php _e('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.', 'wp-webauthn');?></p>
|
||||
<p class="description"><?php esc_html_e('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.', 'wp-webauthn');?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="show_authenticator_type"><?php esc_html_e('Allow users to choose authenticator type', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_sat=wwa_get_option('show_authenticator_type');
|
||||
if($wwa_v_sat === false){
|
||||
wwa_update_option('show_authenticator_type', 'true');
|
||||
$wwa_v_sat = 'true';
|
||||
}
|
||||
?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="show_authenticator_type" value="true" <?php if($wwa_v_sat === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="show_authenticator_type" value="false" <?php if($wwa_v_sat === 'false'){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('When enabled, users can select the authenticator type when registering.<br>The "Allow a specific type" restriction above still applies regardless of this setting.', 'wp-webauthn'), array('br' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<?php } ?>
|
||||
<tr>
|
||||
<th scope="row"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="password_reset"><?php _e('Disable password reset for', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="password_reset"><?php esc_html_e('Disable password reset for', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_pr=wwa_get_option('password_reset');
|
||||
if($wwa_v_pr === false){
|
||||
@ -262,37 +385,53 @@ if($wwa_v_pr === false){
|
||||
}
|
||||
?>
|
||||
<select name="password_reset" id="password_reset">
|
||||
<option value="off"<?php if($wwa_v_pr === 'off'){?> selected<?php }?>><?php _e('Off', 'wp-webauthn');?></option>
|
||||
<option value="admin"<?php if($wwa_v_pr === 'admin'){?> selected<?php }?>><?php _e('Everyone except administrators', 'wp-webauthn');?></option>
|
||||
<option value="all"<?php if($wwa_v_pr === 'all'){?> selected<?php }?>><?php _e('Everyone', 'wp-webauthn');?></option>
|
||||
<option value="off"<?php if($wwa_v_pr === 'off'){?> selected<?php }?>><?php esc_html_e('Off', 'wp-webauthn');?></option>
|
||||
<option value="admin"<?php if($wwa_v_pr === 'admin'){?> selected<?php }?>><?php esc_html_e('Everyone except administrators', 'wp-webauthn');?></option>
|
||||
<option value="all"<?php if($wwa_v_pr === 'all'){?> selected<?php }?>><?php esc_html_e('Everyone', 'wp-webauthn');?></option>
|
||||
</select>
|
||||
<p class="description"><?php _e('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".<br>If "Everyone except administrators" is selected, only administrators with the "Edit user" permission will be able to update passwords (for all users).', 'wp-webauthn');?></p>
|
||||
<p class="description"><?php echo wp_kses(__('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".<br>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()));?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="after_user_registration"><?php _e('After User Registration', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="after_user_registration"><?php esc_html_e('After User Registration', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_aur=wwa_get_option('after_user_registration');
|
||||
if($wwa_v_aur === false){
|
||||
wwa_update_option('after_user_registration', 'none');
|
||||
$wwa_v_aur = 'none';
|
||||
}
|
||||
/** @disregard P1009 Undefined type */
|
||||
if($wwa_v_aur === 'mail' && (!function_exists('\WPWebAuthn\OTML\loaded') || !\WPWebAuthn\OTML\loaded())){
|
||||
wwa_update_option('after_user_registration', 'none');
|
||||
$wwa_v_aur = 'none';
|
||||
}
|
||||
?>
|
||||
<select name="after_user_registration" id="after_user_registration">
|
||||
<option value="none"<?php if($wwa_v_aur === 'none'){?> selected<?php }?>><?php _e('No action', 'wp-webauthn');?></option>
|
||||
<option value="login"<?php if($wwa_v_aur === 'login'){?> selected<?php }?>><?php _e('Log user in and redirect to user\'s profile', 'wp-webauthn');?></option>
|
||||
<option value="none"<?php if($wwa_v_aur === 'none'){?> selected<?php }?>><?php esc_html_e('No action', 'wp-webauthn');?></option>
|
||||
<option value="login"<?php if($wwa_v_aur === 'login'){?> selected<?php }?>><?php esc_html_e('Log user in and redirect to user\'s profile', 'wp-webauthn');?></option>
|
||||
<?php if(function_exists('\WPWebAuthn\OTML\loaded') && \WPWebAuthn\OTML\loaded()){ /** @disregard P1009 Undefined type */ ?>
|
||||
<option value="mail"<?php if($wwa_v_aur === 'mail'){?> selected<?php }?>><?php esc_html_e('Send user an one-time login link via email', 'wp-webauthn');?></option>
|
||||
<?php } ?>
|
||||
</select>
|
||||
<p class="description"><?php _e('What to do when a new user registered.<br>By default, new users have to login manually after registration. If "WebAuthn Only" is enabled, they will not be able to login.<br>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');?></p>
|
||||
<p class="description"><?php echo wp_kses(__('What to do when a new user registered.<br>By default, new users have to login manually after registration. If "WebAuthn Only" is enabled, they will not be able to login.<br>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()));?>
|
||||
<?php
|
||||
/** @disregard P1009 Undefined type */
|
||||
if(function_exists('\WPWebAuthn\OTML\loaded') && \WPWebAuthn\OTML\loaded()){
|
||||
echo wp_kses(__('<br>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.<br><strong>"Send login link" will work even if "Allow user login by login link via email" is disabled.</strong>', 'wp-webauthn'), array('br' => array(), 'strong' => array()));
|
||||
}
|
||||
?>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"></th>
|
||||
</tr>
|
||||
<?php do_action('wwa_admin_page_extra'); ?>
|
||||
<tr>
|
||||
<th scope="row"><label for="logging"><?php _e('Logging', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="logging"><?php esc_html_e('Logging', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_log=wwa_get_option('logging');
|
||||
if($wwa_v_log === false){
|
||||
@ -301,12 +440,12 @@ if($wwa_v_log === false){
|
||||
}
|
||||
?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="logging" value="true" <?php if($wwa_v_log === 'true'){?>checked="checked"<?php }?>> <?php _e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="logging" value="false" <?php if($wwa_v_log === 'false'){?>checked="checked"<?php }?>> <?php _e("Disable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="logging" value="true" <?php if($wwa_v_log === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="logging" value="false" <?php if($wwa_v_log === 'false'){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p>
|
||||
<button id="clear_log" class="button" <?php $log = get_option('wwa_log');if($log === false || ($log !== false && count($log) === 0)){?> disabled<?php }?>><?php _e('Clear log', 'wp-webauthn');?></button> <span id="log-count"><?php echo __("Log count: ", "wp-webauthn").($log === false ? "0" : strval(count($log)));?></span>
|
||||
<button id="clear_log" class="button" <?php $log = get_option('wwa_log');if($log === false || ($log !== false && count($log) === 0)){?> disabled<?php }?>><?php esc_html_e('Clear log', 'wp-webauthn');?></button> <span id="log-count"><?php echo esc_html(__('Log count: ', 'wp-webauthn') . ($log === false ? '0' : strval(count($log))));?></span>
|
||||
</p>
|
||||
<p class="description"><?php _e('For debugging only. Enable only when needed.<br><strong>Note: Logs may contain sensitive information.</strong>', 'wp-webauthn');?></p>
|
||||
<p class="description"><?php echo wp_kses(__('For debugging only. Enable only when needed.<br><strong>Note: Logs may contain sensitive information.</strong>', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
@ -315,12 +454,12 @@ if($wwa_v_log === false){
|
||||
if(wwa_get_option('logging') === 'true' || ($log !== false && count($log) > 0)){
|
||||
?>
|
||||
<div<?php if(wwa_get_option('logging') !== 'true'){?> id="wwa-remove-log"<?php }?>>
|
||||
<h2><?php _e('Log', 'wp-webauthn');?></h2>
|
||||
<textarea name="wwa_log" id="wwa_log" rows="20" cols="108" readonly><?php echo get_option("wwa_log") === false ? "" : implode("\n", get_option("wwa_log"));?></textarea>
|
||||
<p class="description"><?php _e('Automatic update every 5 seconds.', 'wp-webauthn');?></p>
|
||||
<h2><?php esc_html_e('Log', 'wp-webauthn');?></h2>
|
||||
<textarea name="wwa_log" id="wwa_log" rows="20" cols="108" readonly><?php echo get_option("wwa_log") === false ? "" : esc_textarea(implode("\n", get_option("wwa_log")));?></textarea>
|
||||
<p class="description"><?php esc_html_e('Automatic update every 5 seconds.', 'wp-webauthn');?></p>
|
||||
<br>
|
||||
</div>
|
||||
<?php }}
|
||||
/* translators: %s: admin profile url */ ?>
|
||||
<p class="description"><?php printf(__('To register a new authenticator or edit your authenticators, please go to <a href="%s#wwa-webauthn-start">your profile</a>.', 'wp-webauthn'), admin_url('profile.php'));?></p>
|
||||
<p class="description"><?php echo wp_kses(sprintf(__('To register a new authenticator or edit your authenticators, please go to <a href="%s#wwa-webauthn-start">your profile</a>.', 'wp-webauthn'), esc_url(admin_url('profile.php'))), array('a' => array('href' => array())));?></p>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
require_once('wp-webauthn-vendor/autoload.php');
|
||||
use Webauthn\Server;
|
||||
use Webauthn\PublicKeyCredentialRpEntity;
|
||||
@ -7,169 +11,257 @@ use Webauthn\PublicKeyCredentialCreationOptions;
|
||||
use Webauthn\PublicKeyCredentialSourceRepository as PublicKeyCredentialSourceRepositoryInterface;
|
||||
use Webauthn\PublicKeyCredentialSource;
|
||||
use Webauthn\AuthenticatorSelectionCriteria;
|
||||
use Webauthn\PublicKeyCredentialDescriptor;
|
||||
use Nyholm\Psr7\Factory\Psr17Factory;
|
||||
use Nyholm\Psr7Server\ServerRequestCreator;
|
||||
|
||||
/**
|
||||
* Store all publickeys and pubilckey metas
|
||||
*/
|
||||
class PublicKeyCredentialSourceRepository implements PublicKeyCredentialSourceRepositoryInterface {
|
||||
// Get one credential by credential ID
|
||||
private $registration_context = null;
|
||||
|
||||
public function findOneByCredentialId(string $publicKeyCredentialId): ?PublicKeyCredentialSource {
|
||||
$data = $this->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.");
|
||||
}
|
||||
|
||||
@ -1,5 +1,61 @@
|
||||
<?php
|
||||
// Two Factor
|
||||
if(has_action('wp_login', array('Two_Factor_Core', 'wp_login')) !== false){
|
||||
remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), 10, 2);
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!function_exists('wwa_is_webauthn_ajax_login_request')) {
|
||||
function wwa_is_webauthn_ajax_login_request(): bool
|
||||
{
|
||||
if (!function_exists('wp_doing_ajax') || !wp_doing_ajax()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$action = isset($_REQUEST['action'])
|
||||
? sanitize_text_field(wp_unslash($_REQUEST['action']))
|
||||
: '';
|
||||
|
||||
return in_array($action, array('wwa_auth_start', 'wwa_auth'), true);
|
||||
}
|
||||
}
|
||||
|
||||
if (wwa_is_webauthn_ajax_login_request() && class_exists('Two_Factor_Core')) {
|
||||
|
||||
/**
|
||||
* 1) Prevent Two-Factor from redirecting passwordless WebAuthn logins
|
||||
* into its own wp_login challenge flow.
|
||||
*/
|
||||
$prio = has_action('wp_login', array('Two_Factor_Core', 'wp_login'));
|
||||
if ($prio !== false) {
|
||||
remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), $prio);
|
||||
}
|
||||
|
||||
// Defensive cleanup for common / unexpected priorities.
|
||||
remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), 1);
|
||||
remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), 10);
|
||||
remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), 100);
|
||||
remove_action('wp_login', array('Two_Factor_Core', 'wp_login'), PHP_INT_MAX);
|
||||
|
||||
/**
|
||||
* 2) Prevent Two-Factor from reporting enabled providers during
|
||||
* the passwordless WebAuthn AJAX auth flow only.
|
||||
*
|
||||
* This keeps Two-Factor fully active for normal password logins.
|
||||
*/
|
||||
add_filter('two_factor_enabled_providers_for_user', function ($enabled, $user_id) {
|
||||
return array();
|
||||
}, 9, 2);
|
||||
|
||||
/**
|
||||
* 3) If Two-Factor previously blocked auth cookies in this request,
|
||||
* allow them again so WP-WebAuthn can complete login successfully.
|
||||
*/
|
||||
$cookie_prio = has_filter('send_auth_cookies', '__return_false');
|
||||
if ($cookie_prio !== false) {
|
||||
remove_filter('send_auth_cookies', '__return_false', $cookie_prio);
|
||||
}
|
||||
|
||||
remove_filter('send_auth_cookies', '__return_false', 31);
|
||||
remove_filter('send_auth_cookies', '__return_false', 100);
|
||||
remove_filter('send_auth_cookies', '__return_false', PHP_INT_MAX);
|
||||
}
|
||||
|
||||
@ -1,4 +1,8 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// WordPress transient adapter
|
||||
function wwa_set_temp_val($name, $value, $client_id){
|
||||
return set_transient('wwa_'.$name.$client_id, serialize($value), 90);
|
||||
@ -6,7 +10,7 @@ function wwa_set_temp_val($name, $value, $client_id){
|
||||
|
||||
function wwa_get_temp_val($name, $client_id){
|
||||
$val = get_transient('wwa_'.$name.$client_id);
|
||||
return $val === false ? false : unserialize($val);
|
||||
return $val === false ? false : maybe_unserialize($val);
|
||||
}
|
||||
|
||||
function wwa_delete_temp_val($name, $client_id){
|
||||
@ -37,6 +41,10 @@ function wwa_init_new_options(){
|
||||
if(wwa_get_option('allow_authenticator_type') === false){
|
||||
wwa_update_option('allow_authenticator_type', 'none');
|
||||
}
|
||||
// Existing installs default to 'true' to preserve previous behaviour
|
||||
if(wwa_get_option('show_authenticator_type') === false){
|
||||
wwa_update_option('show_authenticator_type', 'true');
|
||||
}
|
||||
if(wwa_get_option('remember_me') === false){
|
||||
wwa_update_option('remember_me', 'false');
|
||||
}
|
||||
@ -52,6 +60,12 @@ function wwa_init_new_options(){
|
||||
if(wwa_get_option('after_user_registration') === false){
|
||||
wwa_update_option('after_user_registration', 'none');
|
||||
}
|
||||
if(wwa_get_option('terminology') === false){
|
||||
wwa_update_option('terminology', 'webauthn');
|
||||
}
|
||||
if(wwa_get_option('ror_origins') === false){
|
||||
wwa_update_option('ror_origins', '');
|
||||
}
|
||||
}
|
||||
|
||||
// Create random strings for user ID
|
||||
@ -80,7 +94,7 @@ function wwa_add_log($id, $content = '', $init = false){
|
||||
if($log === false){
|
||||
$log = array();
|
||||
}
|
||||
$log[] = '['.current_time('mysql').']['.$id.'] '.$content;
|
||||
$log[] = '['.current_time('mysql').']['.$id.'] '.wp_strip_all_tags($content);
|
||||
update_option('wwa_log', $log);
|
||||
}
|
||||
|
||||
@ -104,49 +118,72 @@ function wwa_generate_call_trace($exception = false){
|
||||
return "Traceback:\n ".implode("\n ", $result);
|
||||
}
|
||||
|
||||
// Delete all credentials when deleting user
|
||||
function wwa_cleanup_blog_credentials($user_id, $blog_id){
|
||||
global $wpdb;
|
||||
$wpdb->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' => __('<strong>Error</strong>: The username field is empty.', 'wp-webauthn'),
|
||||
'i18n_12' => '<span class="wwa-try-username">'.__('Try to enter the username', 'wp-webauthn').'</span>'
|
||||
'i18n_12' => '<span class="wwa-try-username">'.__('Try to enter the username', 'wp-webauthn').'</span>',
|
||||
'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){ ?>
|
||||
<div class="notice notice-warning">
|
||||
<?php /* translators: %s: 'the site' or 'your account', and admin profile url */ ?>
|
||||
<p><?php printf(__('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. <a href="%s#wwa-webauthn-start">Register</a>', 'wp-webauthn'), $first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('your account', 'wp-webauthn'), admin_url('profile.php'));?></p>
|
||||
<?php
|
||||
$wwa_scope_label = esc_html($first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('your account', 'wp-webauthn'));
|
||||
$wwa_cred_label = esc_html(wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticator', 'wp-webauthn') : __('passkey', 'wp-webauthn'));
|
||||
/* translators: %1$s: 'the site' or 'your account', %2$s: 'WebAuthn authenticator' or 'passkey', %3$s: admin profile url */
|
||||
?>
|
||||
<p><?php echo wp_kses(sprintf(__('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. <a href="%3$s#wwa-webauthn-start">Register</a>', 'wp-webauthn'), $wwa_scope_label, $wwa_cred_label, esc_url(admin_url('profile.php'))), array('a' => array('href' => array())));?></p>
|
||||
<?php if(is_multisite() && !is_subdomain_install()){
|
||||
/* translators: %s: 'WebAuthn authenticators' or 'Passkeys' */ ?>
|
||||
<p><?php echo esc_html(sprintf(__('%s registered on other sites within this network may also be used to log in.', 'wp-webauthn'), wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticators', 'wp-webauthn') : __('Passkeys', 'wp-webauthn'))); ?></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php }
|
||||
}
|
||||
// Check other user
|
||||
|
||||
global $pagenow;
|
||||
if($pagenow == 'user-edit.php' && isset($_GET['user_id']) && intval($_GET['user_id']) !== $user_info->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){ ?>
|
||||
<div class="notice notice-warning">
|
||||
<?php /* translators: %s: 'the site' or 'your account' */ ?>
|
||||
<p><?php printf(__('Logging in with password has been disabled for %s but <strong>this account</strong> 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'));?></p>
|
||||
<?php
|
||||
$wwa_scope_label = esc_html($first_choice === 'webauthn' ? __('the site', 'wp-webauthn') : __('this account', 'wp-webauthn'));
|
||||
$wwa_cred_label = esc_html(wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticator', 'wp-webauthn') : __('passkey', 'wp-webauthn'));
|
||||
/* translators: %1$s: 'the site' or 'this account', %2$s: 'WebAuthn authenticator' or 'passkey' */
|
||||
?>
|
||||
<p><?php echo wp_kses(sprintf(__('Logging in with password has been disabled for %1$s but <strong>this account</strong> 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()));?></p>
|
||||
<?php if(is_multisite() && !is_subdomain_install()){
|
||||
/* translators: %s: 'WebAuthn authenticators' or 'Passkeys' */ ?>
|
||||
<p><?php echo esc_html(sprintf(__('%s registered on other sites within this network may also be used to log in.', 'wp-webauthn'), wwa_get_option('terminology') === 'webauthn' ? __('WebAuthn authenticators', 'wp-webauthn') : __('Passkeys', 'wp-webauthn'))); ?></p>
|
||||
<?php } ?>
|
||||
</div>
|
||||
<?php }
|
||||
}
|
||||
@ -353,17 +380,27 @@ function wwa_settings_link($links_array, $plugin_file_name){
|
||||
}
|
||||
add_filter('plugin_action_links', 'wwa_settings_link', 10, 2);
|
||||
|
||||
function wwa_network_settings_link($links_array, $plugin_file_name){
|
||||
if($plugin_file_name === 'wp-webauthn/wp-webauthn.php'){
|
||||
$links_array[] = '<a href="'.esc_url(network_admin_url('settings.php?page=wwa_network_admin')).'">'.__('Network Settings', 'wp-webauthn').'</a>';
|
||||
}
|
||||
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[] = '<a href="https://github.com/yrccondor/wp-webauthn">'.__('GitHub', 'wp-webauthn').'</a>';
|
||||
$links_array[] = '<a href="http://doc.flyhigher.top/wp-webauthn">'.__('Documentation', 'wp-webauthn').'</a>';
|
||||
$links_array[] = '<a href="https://doc.flyhigher.top/wp-webauthn">'.__('Documentation', 'wp-webauthn').'</a>';
|
||||
}
|
||||
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);
|
||||
|
||||
@ -1,7 +1,11 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Add menu
|
||||
function wwa_admin_menu(){
|
||||
add_options_page('WP-WebAuthn' , 'WP-WebAuthn', 'read', 'wwa_admin','wwa_display_main_menu');
|
||||
add_options_page('WP-WebAuthn' , 'WP-WebAuthn', 'manage_options', 'wwa_admin','wwa_display_main_menu');
|
||||
}
|
||||
function wwa_display_main_menu(){
|
||||
include('wwa-admin-content.php');
|
||||
@ -28,11 +32,11 @@ function wwa_save_user_profile_fields($user_id){
|
||||
}
|
||||
|
||||
if(!isset($_POST['webauthn_only'])){
|
||||
update_user_meta($user_id, 'webauthn_only', 'false');
|
||||
update_user_meta($user_id, 'wwa_webauthn_only', 'false');
|
||||
}elseif(sanitize_text_field(wp_unslash($_POST['webauthn_only'])) === 'true'){
|
||||
update_user_meta($user_id, 'webauthn_only', 'true');
|
||||
update_user_meta($user_id, 'wwa_webauthn_only', 'true');
|
||||
}else{
|
||||
update_user_meta($user_id, 'webauthn_only', 'false');
|
||||
update_user_meta($user_id, 'wwa_webauthn_only', 'false');
|
||||
}
|
||||
}
|
||||
add_action('personal_options_update', 'wwa_save_user_profile_fields');
|
||||
@ -48,3 +52,12 @@ function wwa_user_profile_fields_check(){
|
||||
}
|
||||
}
|
||||
add_action('plugins_loaded', 'wwa_user_profile_fields_check');
|
||||
|
||||
function wwa_network_admin_menu(){
|
||||
add_submenu_page('settings.php', 'WP-WebAuthn', 'WP-WebAuthn', 'manage_network_options', 'wwa_network_admin', 'wwa_display_network_settings');
|
||||
}
|
||||
if(is_multisite()){
|
||||
include('wwa-network-admin-content.php');
|
||||
add_action('network_admin_menu', 'wwa_network_admin_menu');
|
||||
add_action('network_admin_edit_wwa_network_options_update', 'wwa_handle_network_options_save');
|
||||
}
|
||||
|
||||
198
wp-content/plugins/wp-webauthn/wwa-network-admin-content.php
Normal file
198
wp-content/plugins/wp-webauthn/wwa-network-admin-content.php
Normal file
@ -0,0 +1,198 @@
|
||||
<?php
|
||||
if(!defined('ABSPATH')){
|
||||
exit;
|
||||
}
|
||||
|
||||
// Handle network options save
|
||||
function wwa_handle_network_options_save(){
|
||||
if(!current_user_can('manage_network_options')){
|
||||
wp_die(esc_html__('You do not have sufficient permissions to access this page.'));
|
||||
}
|
||||
|
||||
check_admin_referer('wwa_network_options_update', 'wwa_network_options_nonce');
|
||||
|
||||
$res_id = wwa_generate_random_string(5);
|
||||
|
||||
$post_first_choice = sanitize_text_field(wp_unslash($_POST['first_choice'] ?? 'true'));
|
||||
if(!in_array($post_first_choice, array('true', 'webauthn', 'false'), true)){
|
||||
$post_first_choice = 'true';
|
||||
}
|
||||
if($post_first_choice !== wwa_get_option('first_choice')){
|
||||
wwa_add_log($res_id, 'network first_choice: "'.wwa_get_option('first_choice').'"->"'.$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 <strong>recommend you to enable it</strong> 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;
|
||||
}
|
||||
?>
|
||||
|
||||
<div class="wrap">
|
||||
<h1>WP-WebAuthn <?php esc_html_e('Network Settings', 'wp-webauthn');?></h1>
|
||||
|
||||
<?php if(isset($_GET['updated']) && $_GET['updated'] === 'true'){ ?>
|
||||
<div class="notice notice-success is-dismissible"><p><?php esc_html_e('Settings saved.', 'wp-webauthn');?></p></div>
|
||||
<?php } ?>
|
||||
|
||||
<p class="description"><?php esc_html_e('These settings apply to all sites in the network.', 'wp-webauthn');?></p>
|
||||
|
||||
<form method="post" action="<?php echo esc_url(network_admin_url('edit.php?action=wwa_network_options_update')); ?>">
|
||||
<?php wp_nonce_field('wwa_network_options_update', 'wwa_network_options_nonce'); ?>
|
||||
<table class="form-table" role="presentation">
|
||||
<tr>
|
||||
<th scope="row"><label for="first_choice"><?php esc_html_e('Preferred login method', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_first_choice=wwa_get_option('first_choice');?>
|
||||
<select name="first_choice" id="first_choice">
|
||||
<option value="true"<?php if($wwa_v_first_choice !== 'false' && !($wwa_v_first_choice === 'webauthn' && !$wwa_not_allowed)){?> selected<?php }?>><?php esc_html_e('Prefer WebAuthn', 'wp-webauthn');?></option>
|
||||
<option value="false"<?php if($wwa_v_first_choice === 'false'){?> selected<?php }?>><?php esc_html_e('Prefer password', 'wp-webauthn');?></option>
|
||||
<option value="webauthn"<?php if($wwa_v_first_choice === 'webauthn' && !$wwa_not_allowed){?> selected<?php }if($wwa_not_allowed){?> disabled<?php }?>><?php esc_html_e('WebAuthn Only', 'wp-webauthn');?></option>
|
||||
</select>
|
||||
<p class="description"><?php echo wp_kses(__('When using "WebAuthn Only", password login will be completely disabled. Please make sure your browser supports WebAuthn, otherwise you may unable to login.<br>User that doesn\'t have any registered authenticator (e.g. new user) will unable to login when using "WebAuthn Only".<br>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()));?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"></th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="user_verification"><?php esc_html_e('Require user verification', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_uv=wwa_get_option('user_verification');?>
|
||||
<fieldset id="wwa-uv-field">
|
||||
<label><input type="radio" name="user_verification" value="true" <?php if($wwa_v_uv === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="user_verification" value="false" <?php if($wwa_v_uv === 'false'){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('User verification can improve security, but is not fully supported by mobile devices. <br> If you cannot register or verify your authenticators, please consider disabling user verification.', 'wp-webauthn'), array('br' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="usernameless_login"><?php esc_html_e('Allow to login without username', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_ul=wwa_get_option('usernameless_login');?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="usernameless_login" value="true" <?php if($wwa_v_ul === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="usernameless_login" value="false" <?php if($wwa_v_ul === 'false' || $wwa_v_ul === false){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('Allow users to register authenticator with usernameless authentication feature and login without username.<br><strong>User verification will be enabled automatically when authenticating with usernameless authentication feature.</strong><br>Some authenticators and some browsers <strong>DO NOT</strong> support this feature.', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="allow_authenticator_type"><?php esc_html_e('Allow a specific type of authenticator', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_at=wwa_get_option('allow_authenticator_type');?>
|
||||
<select name="allow_authenticator_type" id="allow_authenticator_type">
|
||||
<option value="none"<?php if($wwa_v_at === 'none' || $wwa_v_at === false){?> selected<?php }?>><?php esc_html_e('Any', 'wp-webauthn');?></option>
|
||||
<option value="platform"<?php if($wwa_v_at === 'platform'){?> selected<?php }?>><?php esc_html_e('Platform (e.g. Passkey or built-in sensors)', 'wp-webauthn');?></option>
|
||||
<option value="cross-platform"<?php if($wwa_v_at === 'cross-platform'){?> selected<?php }?>><?php esc_html_e('Roaming (e.g. USB security keys)', 'wp-webauthn');?></option>
|
||||
</select>
|
||||
<p class="description"><?php esc_html_e('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.', 'wp-webauthn');?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"><label for="show_authenticator_type"><?php esc_html_e('Allow users to choose authenticator type', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_sat=wwa_get_option('show_authenticator_type');?>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="show_authenticator_type" value="true" <?php if($wwa_v_sat === 'true'){?>checked="checked"<?php }?>> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="show_authenticator_type" value="false" <?php if($wwa_v_sat === 'false' || $wwa_v_sat === false){?>checked="checked"<?php }?>> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('When enabled, users can select the authenticator type when registering.<br>The "Allow a specific type" restriction above still applies regardless of this setting.', 'wp-webauthn'), array('br' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row"></th>
|
||||
</tr>
|
||||
<!-- Feature not fully ready <tr>
|
||||
<th scope="row"><label for="ror_origins"><?php esc_html_e('Related origins', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php $wwa_v_ror = wwa_get_option('ror_origins');?>
|
||||
<textarea name="ror_origins" id="ror_origins" rows="4" cols="50" class="large-text code"><?php echo esc_textarea($wwa_v_ror ? $wwa_v_ror : '');?></textarea>
|
||||
<p class="description"><?php echo wp_kses(__('Allow cross-site passkey usages (<a href="https://passkeys.dev/docs/advanced/related-origins/" target="_blank">Related Origin Requests</a>). May be useful for multi-site networks.<br> Enter one origin per line (e.g. <code>https://example.com</code>). Leave empty to disable.', 'wp-webauthn'), array('a' => array('href' => array(), 'target' => array()), 'br' => array(), 'code' => array()));?></p>
|
||||
</td>
|
||||
</tr>-->
|
||||
</table>
|
||||
<?php submit_button(); ?>
|
||||
</form>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
@ -1,8 +1,15 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$wwa_term = wwa_get_option('terminology') === 'webauthn';
|
||||
|
||||
// Insert CSS and JS
|
||||
wp_enqueue_script('wwa_profile', plugins_url('js/profile.js', __FILE__));
|
||||
wp_enqueue_script('wwa_profile', plugins_url('js/profile.js', __FILE__), array(), get_option('wwa_version')['version']);
|
||||
wp_localize_script('wwa_profile', 'php_vars', array(
|
||||
'ajax_url' => 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' => '<span class="wwa-failed">'.__('Verification failed', 'wp-webauthn').'</span>',
|
||||
'i18n_16' => '<span class="wwa-success">'.__('Verification passed! You can now log in through WebAuthn', 'wp-webauthn').'</span>',
|
||||
'i18n_16' => '<span class="wwa-success">'.(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')).'</span>',
|
||||
'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'))
|
||||
));
|
||||
?>
|
||||
<br>
|
||||
<h2 id="wwa-webauthn-start">WebAuthn</h2>
|
||||
<h2 id="wwa-webauthn-start"><?php if($wwa_term){ ?>WebAuthn<?php }else{ esc_html_e('Passkeys', 'wp-webauthn'); }?></h2>
|
||||
<?php
|
||||
if(isset($_GET['wwa_registered']) && $_GET['wwa_registered'] === 'true'){
|
||||
$count = 0;
|
||||
if(user_can($user, 'read')){
|
||||
$user_ids = wwa_get_option("user_id");
|
||||
if(isset($user_ids[$user->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){
|
||||
?>
|
||||
<div id="wp-webauthn-message-container">
|
||||
<div class="notice notice-info is-dismissible" role="alert" id="wp-webauthn-message">
|
||||
<p><?php _e('You\'ve successfully registered! Now you can register your authenticators below.', 'wp-webauthn')?></p>
|
||||
<p><?php
|
||||
$wwa_term_plural = $wwa_term ? __('authenticators', 'wp-webauthn') : __('passkeys', 'wp-webauthn');
|
||||
/* translators: %1$s: 'authenticators' or 'passkeys' */
|
||||
echo esc_html(sprintf(__('You\'ve successfully registered! Now you can register your %1$s below.', 'wp-webauthn'), $wwa_term_plural));
|
||||
?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
@ -73,103 +82,112 @@ if(!function_exists("mb_substr") || !function_exists("gmp_intval") || !wwa_check
|
||||
?>
|
||||
<div id="wp-webauthn-error-container">
|
||||
<div class="notice notice-error is-dismissible" role="alert" id="wp-webauthn-error">
|
||||
<p><?php _e('This site is not correctly configured to use WebAuthn. Please contact the site administrator.', 'wp-webauthn')?></p>
|
||||
<p><?php
|
||||
/* translators: %s: 'WebAuthn' or 'passkey' */
|
||||
echo esc_html(sprintf(__('This site is not correctly configured to use %s. Please contact the site administrator.', 'wp-webauthn'), $wwa_term ? 'WebAuthn' : __('Passkey', 'wp-webauthn')));
|
||||
?></p>
|
||||
</div>
|
||||
</div>
|
||||
<?php } ?>
|
||||
<table class="form-table">
|
||||
<tr class="user-rich-editing-wrap">
|
||||
<th scope="row"><?php _e('WebAuthn Only', 'wp-webauthn');?></th>
|
||||
<th scope="row"><?php $wwa_term ? esc_html_e('WebAuthn Only', 'wp-webauthn') : esc_html_e('Passkey Only', 'wp-webauthn'); ?></th>
|
||||
<td>
|
||||
<label for="webauthn_only">
|
||||
<?php $wwa_v_first_choice = wwa_get_option('first_choice');?>
|
||||
<input name="webauthn_only" type="checkbox" id="webauthn_only" value="true"<?php if(!$wwa_not_allowed){if($wwa_v_first_choice === 'webauthn'){echo ' disabled checked';}else{if(get_the_author_meta('webauthn_only', $user->ID) === 'true'){echo ' checked';}}}else{echo ' disabled';} ?>> <?php _e('Disable password login for this account', 'wp-webauthn');?>
|
||||
<input name="webauthn_only" type="checkbox" id="webauthn_only" value="true"<?php if(!$wwa_not_allowed){if($wwa_v_first_choice === 'webauthn'){echo ' disabled checked';}else{if(get_user_meta($user->ID, 'wwa_webauthn_only', true) === 'true'){echo ' checked';}}}else{echo ' disabled';} ?>> <?php esc_html_e('Disable password login for this account', 'wp-webauthn');?>
|
||||
</label>
|
||||
<p class="description"><?php _e('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.', 'wp-webauthn');if($wwa_v_first_choice === 'webauthn' && !$wwa_not_allowed){?><br><strong><?php _e('The site administrator has disabled password login for the whole site.', 'wp-webauthn');?></strong><?php }?></p>
|
||||
<p class="description"><?php $wwa_term ? esc_html_e('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.', 'wp-webauthn') : esc_html_e('When checked, password login will be completely disabled. Please make sure you have a registered passkey, otherwise you may unable to login.', 'wp-webauthn');if(is_multisite()){?><br><?php esc_html_e('This setting applies to your account across all sites in the network.', 'wp-webauthn');} if($wwa_v_first_choice === 'webauthn' && !$wwa_not_allowed){?><br><strong><?php esc_html_e('The site administrator has disabled password login for the whole site.', 'wp-webauthn');?></strong><?php }?></p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<h3><?php _e('Registered WebAuthn Authenticators', 'wp-webauthn');?></h3>
|
||||
<h3><?php $wwa_term ? esc_html_e('Registered WebAuthn Authenticators', 'wp-webauthn') : esc_html_e('Registered Passkeys', 'wp-webauthn'); ?></h3>
|
||||
<div class="wwa-table">
|
||||
<table class="wp-list-table widefat fixed striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th><?php _e('Identifier', 'wp-webauthn');?></th>
|
||||
<th><?php _e('Type', 'wp-webauthn');?></th>
|
||||
<th><?php _ex('Registered', 'time', 'wp-webauthn');?></th>
|
||||
<th><?php _e('Last used', 'wp-webauthn');?></th>
|
||||
<th class="wwa-usernameless-th"><?php _e('Usernameless', 'wp-webauthn');?></th>
|
||||
<th><?php _e('Action', 'wp-webauthn');?></th>
|
||||
<th><?php esc_html_e('Identifier', 'wp-webauthn');?></th>
|
||||
<?php if(wwa_get_option('show_authenticator_type') !== 'false'){?><th class="wwa-type-th"><?php esc_html_e('Type', 'wp-webauthn');?></th><?php }?>
|
||||
<th><?php echo esc_html(_x('Registered', 'time', 'wp-webauthn'));?></th>
|
||||
<th><?php esc_html_e('Last used', 'wp-webauthn');?></th>
|
||||
<th class="wwa-usernameless-th"><?php esc_html_e('Usernameless', 'wp-webauthn');?></th>
|
||||
<th><?php esc_html_e('Action', 'wp-webauthn');?></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="wwa-authenticator-list">
|
||||
<tr>
|
||||
<td colspan="5"><?php _e('Loading...', 'wp-webauthn');?></td>
|
||||
<td colspan="<?php echo esc_attr(wwa_get_option('show_authenticator_type') !== 'false' ? '5' : '4'); ?>"><?php esc_html_e('Loading...', 'wp-webauthn');?></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<th><?php _e('Identifier', 'wp-webauthn');?></th>
|
||||
<th><?php _e('Type', 'wp-webauthn');?></th>
|
||||
<th><?php _ex('Registered', 'time', 'wp-webauthn');?></th>
|
||||
<th><?php _e('Last used', 'wp-webauthn');?></th>
|
||||
<th class="wwa-usernameless-th"><?php _e('Usernameless', 'wp-webauthn');?></th>
|
||||
<th><?php _e('Action', 'wp-webauthn');?></th>
|
||||
<th><?php esc_html_e('Identifier', 'wp-webauthn');?></th>
|
||||
<?php if(wwa_get_option('show_authenticator_type') !== 'false'){?><th class="wwa-type-th"><?php esc_html_e('Type', 'wp-webauthn');?></th><?php }?>
|
||||
<th><?php echo esc_html(_x('Registered', 'time', 'wp-webauthn'));?></th>
|
||||
<th><?php esc_html_e('Last used', 'wp-webauthn');?></th>
|
||||
<th class="wwa-usernameless-th"><?php esc_html_e('Usernameless', 'wp-webauthn');?></th>
|
||||
<th><?php esc_html_e('Action', 'wp-webauthn');?></th>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<p id="wwa_usernameless_tip"></p>
|
||||
<p id="wwa_type_tip"></p>
|
||||
<button id="wwa-add-new-btn" class="button" title="<?php _e('Register New Authenticator', 'wp-webauthn');?>"<?php if($wwa_not_allowed){echo ' disabled';}?>><?php _e('Register New Authenticator', 'wp-webauthn');?></button> <button id="wwa-verify-btn" class="button" title="<?php _e('Verify Authenticator', 'wp-webauthn');?>"><?php _e('Verify Authenticator', 'wp-webauthn');?></button>
|
||||
<button id="wwa-add-new-btn" class="button" title="<?php $wwa_term ? esc_attr_e('Register New Authenticator', 'wp-webauthn') : esc_attr_e('Register New Passkey', 'wp-webauthn'); ?>"<?php if($wwa_not_allowed){echo ' disabled';}?>><?php $wwa_term ? esc_html_e('Register New Authenticator', 'wp-webauthn') : esc_html_e('Register New Passkey', 'wp-webauthn'); ?></button> <button id="wwa-verify-btn" class="button" title="<?php $wwa_term ? esc_attr_e('Verify Authenticator', 'wp-webauthn') : esc_attr_e('Verify Passkey', 'wp-webauthn'); ?>"><?php $wwa_term ? esc_html_e('Verify Authenticator', 'wp-webauthn') : esc_html_e('Verify Passkey', 'wp-webauthn'); ?></button>
|
||||
<div id="wwa-new-block" tabindex="-1">
|
||||
<button class="button button-small wwa-cancel"><?php _e('Close');?></button>
|
||||
<h2><?php _e('Register New Authenticator', 'wp-webauthn');?></h2>
|
||||
<?php /* translators: %s: user login name */ ?>
|
||||
<p class="description"><?php printf(__('You are about to associate an authenticator with the current account <strong>%s</strong>.<br>You can register multiple authenticators for an account.', 'wp-webauthn'), $user->user_login);?></p>
|
||||
<button class="button button-small wwa-cancel"><?php esc_html_e('Close');?></button>
|
||||
<h2><?php $wwa_term ? esc_html_e('Register New Authenticator', 'wp-webauthn') : esc_html_e('Register New Passkey', 'wp-webauthn'); ?></h2>
|
||||
<?php
|
||||
$wwa_term_singular = esc_html($wwa_term ? __('an authenticator', 'wp-webauthn') : __('a passkey', 'wp-webauthn'));
|
||||
$wwa_term_plural = esc_html($wwa_term ? __('authenticators', 'wp-webauthn') : __('passkeys', 'wp-webauthn'));
|
||||
/* translators: %1$s: 'an authenticator' or 'a passkey', %2$s: user login name, %3$s: 'authenticators' or 'passkeys' */
|
||||
?>
|
||||
<p class="description"><?php echo wp_kses(sprintf(__('You are about to associate %1$s with the current account <strong>%2$s</strong>.<br>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()));?></p>
|
||||
<table class="form-table">
|
||||
<?php if(wwa_get_option('show_authenticator_type') !== 'false'){?>
|
||||
<tr>
|
||||
<th scope="row"><label for="wwa_authenticator_type"><?php _e('Type of authenticator', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="wwa_authenticator_type"><?php esc_html_e('Type of authenticator', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<?php
|
||||
$allowed_type = wwa_get_option('allow_authenticator_type') === false ? 'none' : wwa_get_option('allow_authenticator_type');
|
||||
?>
|
||||
<select name="wwa_authenticator_type" id="wwa_authenticator_type">
|
||||
<option value="none" id="type-none" class="sub-type"<?php if($allowed_type !== 'none'){echo ' disabled';}?>><?php _e('Any', 'wp-webauthn');?></option>
|
||||
<option value="platform" id="type-platform" class="sub-type"<?php if($allowed_type === 'cross-platform'){echo ' disabled';}?>><?php _e('Platform (e.g. built-in fingerprint sensors)', 'wp-webauthn');?></option>
|
||||
<option value="cross-platform" id="type-cross-platform" class="sub-type"<?php if($allowed_type === 'platform'){echo ' disabled';}?>><?php _e('Roaming (e.g. USB security keys)', 'wp-webauthn');?></option>
|
||||
<select name="wwa_authenticator_type" id="wwa_authenticator_type" form="wwa-registration">
|
||||
<option value="none" id="type-none" class="sub-type"<?php if($allowed_type !== 'none'){echo ' disabled';}?>><?php esc_html_e('Any', 'wp-webauthn');?></option>
|
||||
<option value="platform" id="type-platform" class="sub-type"<?php if($allowed_type === 'cross-platform'){echo ' disabled';}?>><?php esc_html_e('Platform (e.g. built-in fingerprint sensors)', 'wp-webauthn');?></option>
|
||||
<option value="cross-platform" id="type-cross-platform" class="sub-type"<?php if($allowed_type === 'platform'){echo ' disabled';}?>><?php esc_html_e('Roaming (e.g. USB security keys)', 'wp-webauthn');?></option>
|
||||
</select>
|
||||
<p class="description"><?php _e('If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn');?></p>
|
||||
<p class="description"><?php echo wp_kses(__('If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn'), array('br' => array()));?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php }?>
|
||||
<tr>
|
||||
<th scope="row"><label for="wwa_authenticator_name"><?php _e('Authenticator Identifier', 'wp-webauthn');?></label></th>
|
||||
<th scope="row"><label for="wwa_authenticator_name"><?php esc_html_e('Authenticator Identifier', 'wp-webauthn');?></label></th>
|
||||
<td>
|
||||
<input name="wwa_authenticator_name" type="text" id="wwa_authenticator_name" class="regular-text">
|
||||
<p class="description"><?php _e('An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway.', 'wp-webauthn');?></p>
|
||||
<input name="wwa_authenticator_name" type="text" id="wwa_authenticator_name" class="regular-text" form="wwa-registration">
|
||||
<p class="description"><?php echo wp_kses(__('An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway.', 'wp-webauthn'), array('strong' => array()));?></p>
|
||||
</td>
|
||||
</tr>
|
||||
<?php if(wwa_get_option('usernameless_login') === "true"){?>
|
||||
<tr>
|
||||
<th scope="row"><label for="wwa_authenticator_usernameless"><?php _e('Login without username', 'wp-webauthn');?></th>
|
||||
<th scope="row"><label for="wwa_authenticator_usernameless"><?php esc_html_e('Login without username', 'wp-webauthn');?></th>
|
||||
<td>
|
||||
<fieldset>
|
||||
<label><input type="radio" name="wwa_authenticator_usernameless" class="wwa_authenticator_usernameless" value="true"> <?php _e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="wwa_authenticator_usernameless" class="wwa_authenticator_usernameless" value="false" checked="checked"> <?php _e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php _e('If registered authenticator with this feature, you can login without enter your username.<br>Some authenticators like U2F-only authenticators and some browsers <strong>DO NOT</strong> support this feature.<br>A record will be stored in the authenticator permanently untill you reset it.', 'wp-webauthn');?></p>
|
||||
<label><input type="radio" name="wwa_authenticator_usernameless" class="wwa_authenticator_usernameless" value="true" form="wwa-registration"> <?php esc_html_e("Enable", "wp-webauthn");?></label><br>
|
||||
<label><input type="radio" name="wwa_authenticator_usernameless" class="wwa_authenticator_usernameless" value="false" checked="checked" form="wwa-registration"> <?php esc_html_e("Disable", "wp-webauthn");?></label><br>
|
||||
<p class="description"><?php echo wp_kses(__('If registered authenticator with this feature, you can login without enter your username.<br>Some authenticators like U2F-only authenticators and some browsers <strong>DO NOT</strong> support this feature.<br>A record will be stored in the authenticator permanently untill you reset it.', 'wp-webauthn'), array('br' => array(), 'strong' => array()));?></p>
|
||||
</fieldset>
|
||||
</td>
|
||||
</tr>
|
||||
<?php }?>
|
||||
</table>
|
||||
<button id="wwa-bind" class="button"><?php _e('Start Registration', 'wp-webauthn');?></button> <span id="wwa-show-progress"></span>
|
||||
<button id="wwa-bind" class="button"><?php esc_html_e('Start Registration', 'wp-webauthn');?></button> <span id="wwa-show-progress"></span>
|
||||
</div>
|
||||
<div id="wwa-verify-block" tabindex="-1">
|
||||
<button class="button button-small wwa-cancel"><?php _e('Close');?></button>
|
||||
<h2><?php _e('Verify Authenticator', 'wp-webauthn');?></h2>
|
||||
<p class="description"><?php _e('Click Test Login to verify that the registered authenticators are working.', 'wp-webauthn');?></p>
|
||||
<button id="wwa-test" class="button"><?php _e('Test Login', 'wp-webauthn');?></button> <span id="wwa-show-test"></span>
|
||||
<button class="button button-small wwa-cancel"><?php esc_html_e('Close');?></button>
|
||||
<h2><?php $wwa_term ? esc_html_e('Verify Authenticator', 'wp-webauthn') : esc_html_e('Verify Passkey', 'wp-webauthn'); ?></h2>
|
||||
<p class="description"><?php esc_html_e('Click Test Login to verify that the registered authenticators are working.', 'wp-webauthn');?></p>
|
||||
<button id="wwa-test" class="button"><?php esc_html_e('Test Login', 'wp-webauthn');?></button> <span id="wwa-show-test"></span>
|
||||
<?php if(wwa_get_option('usernameless_login') === "true"){?>
|
||||
<br><br><button id="wwa-test_usernameless" class="button"><?php _e('Test Login (usernameless)', 'wp-webauthn');?></button> <span id="wwa-show-test-usernameless"></span>
|
||||
<br><br><button id="wwa-test_usernameless" class="button"><?php esc_html_e('Test Login (usernameless)', 'wp-webauthn');?></button> <span id="wwa-show-test-usernameless"></span>
|
||||
<?php }?>
|
||||
</div>
|
||||
@ -1,14 +1,22 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
function wwa_localize_frontend(){
|
||||
wwa_init_new_options();
|
||||
wp_enqueue_script('wwa_frontend_js', plugins_url('js/frontend.js', __FILE__), array(), get_option('wwa_version')['version'], true);
|
||||
wp_localize_script('wwa_frontend_js', 'wwa_php_vars', array(
|
||||
'ajax_url' => 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 = '<div class="wwa-login-form">';
|
||||
|
||||
$args = array('echo' => false, 'value_username' => sanitize_user($username));
|
||||
$args = array('echo' => false, 'value_username' => sanitize_user($atts['username']));
|
||||
$to_wwa = '';
|
||||
if($to !== ""){
|
||||
$args['redirect'] = sanitize_url($to);
|
||||
if($atts['to'] !== ""){
|
||||
$args['redirect'] = sanitize_url($atts['to']);
|
||||
$to_wwa = '<input type="hidden" name="wwa-redirect-to" class="wwa-redirect-to" id="wwa-redirect-to" value="'.$args["redirect"].'">';
|
||||
}
|
||||
|
||||
if($traditional === 'true' && wwa_get_option('first_choice') !== 'webauthn'){
|
||||
$html_form .= '<div class="wwa-login-form-traditional">'.wp_login_form($args).'<br><a class="wwa-t2w" href="#"><span>'.__('Authenticate with WebAuthn', 'wp-webauthn').'</span></a></div>';
|
||||
if($atts['traditional'] === 'true' && wwa_get_option('first_choice') !== 'webauthn'){
|
||||
$html_form .= '<div class="wwa-login-form-traditional">'.wp_login_form($args).'<br><a class="wwa-t2w" href="#"><span>'.(wwa_get_option('terminology') === 'webauthn' ? __('Authenticate with WebAuthn', 'wp-webauthn') : __('Authenticate with a passkey', 'wp-webauthn')).'</span></a></div>';
|
||||
}
|
||||
|
||||
$html_form .= '
|
||||
<div class="wwa-login-form-webauthn">
|
||||
<p class="wwa-login-username">
|
||||
<label for="wwa-user-name">'.(wwa_get_option('email_login') !== 'true' ? __('Username', 'wp-webauthn') : __('Username or Email Address')).'</label>
|
||||
<input type="text" name="wwa-user-name" id="wwa-user-name" class="wwa-user-name" value="'.esc_attr(sanitize_user($username, true)).'" size="20">
|
||||
<input type="text" name="wwa-user-name" id="wwa-user-name" class="wwa-user-name" value="'.esc_attr(sanitize_user($atts['username'], true)).'" size="20">
|
||||
</p>
|
||||
<div class="wp-webauthn-notice">'.__('Authenticate with WebAuthn', 'wp-webauthn').'</div>
|
||||
<div class="wp-webauthn-notice">'.(wwa_get_option('terminology') === 'webauthn' ? __('Authenticate with WebAuthn', 'wp-webauthn') : __('Authenticate with a passkey', 'wp-webauthn')).'</div>
|
||||
<p class="wwa-login-submit-p">'.$to_wwa.'<div class="wwa-form-left">'.((wwa_get_option('remember_me') === false ? 'false' : wwa_get_option('remember_me') !== 'false') ? '<label class="wwa-remember-label"><input name="wwa-rememberme" type="checkbox" id="wwa-rememberme" value="forever"> '.__('Remember Me').'</label>' : '').'<a class="wwa-w2t" href="#">'.__('Authenticate with password', 'wp-webauthn').'</a></div><input type="button" name="wwa-login-submit" id="wwa-login-submit" class="wwa-login-submit button button-primary" value="'.__('Auth', 'wp-webauthn').'"></p>
|
||||
</div>
|
||||
</div>';
|
||||
@ -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 '<div class="wwa-register-form"><p class="wwa-bind">'.__('You haven\'t logged in yet.', 'wp-webauthn').'</p></div>';
|
||||
}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 '
|
||||
<div class="wwa-register-form">
|
||||
$show_type = wwa_get_option('show_authenticator_type') !== 'false';
|
||||
$type_selector = $show_type ? '
|
||||
<label for="wwa-authenticator-type">'.__('Type of authenticator', 'wp-webauthn').'</label>
|
||||
<select name="wwa-authenticator-type" class="wwa-authenticator-type" id="wwa-authenticator-type">
|
||||
<option value="none" class="wwa-type-none"'.($allowed_type !== 'none' ? ' disabled' : '').'>'.__('Any', 'wp-webauthn').'</option>
|
||||
<option value="platform" class="wwa-type-platform"'.($allowed_type === 'cross-platform' ? ' disabled' : '').'>'.__('Platform (e.g. built-in fingerprint sensors)', 'wp-webauthn').'</option>
|
||||
<option value="cross-platform" class="wwa-type-cross-platform"'.($allowed_type === 'platform' ? ' disabled' : '').'>'.__('Roaming (e.g. USB security keys)', 'wp-webauthn').'</option>
|
||||
</select>
|
||||
<p class="wwa-bind-name-description">'.__('If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn').'</p>
|
||||
<p class="wwa-bind-name-description">'.__('If a type is selected, the browser will only prompt for authenticators of selected type. <br> Regardless of the type, you can only log in with the very same authenticators you\'ve registered.', 'wp-webauthn').'</p>' : '';
|
||||
return '
|
||||
<div class="wwa-register-form">'.$type_selector.'
|
||||
<label for="wwa-authenticator-name">'.__('Authenticator identifier', 'wp-webauthn').'</label>
|
||||
<input required name="wwa-authenticator-name" type="text" class="wwa-authenticator-name" id="wwa-authenticator-name">
|
||||
<p class="wwa-bind-name-description">'.__('An easily identifiable name for the authenticator. <strong>DOES NOT</strong> affect the authentication process in anyway.', 'wp-webauthn').'</p>'.(
|
||||
@ -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 '<p class="wwa-test">'.__('You haven\'t logged in yet.', 'wp-webauthn').'</p>';
|
||||
}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 = '<div class="wwa-table-container"><table class="wwa-list-table"><thead><tr><th>'.__('Identifier', 'wp-webauthn').'</th><th>'.__('Type', 'wp-webauthn').'</th><th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></thead><tbody class="wwa-authenticator-list">';
|
||||
$tbody = '<tr><td colspan="5">'.__('Loading...', 'wp-webauthn').'</td></tr>';
|
||||
$tfoot = '</tbody><tfoot><tr><th>'.__('Identifier', 'wp-webauthn').'</th><th>'.__('Type', 'wp-webauthn').'</th><th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></tfoot></table></div><p class="wwa-authenticator-list-usernameless-tip"></p><p class="wwa-authenticator-list-type-tip"></p>';
|
||||
$show_type = wwa_get_option('show_authenticator_type') !== 'false';
|
||||
$type_th = $show_type ? '<th class="wwa-type-th">'.__('Type', 'wp-webauthn').'</th>' : '';
|
||||
$loading_colspan = $show_type ? '5' : '4';
|
||||
$thead = '<div class="wwa-table-container"><table class="wwa-list-table"><thead><tr><th>'.__('Identifier', 'wp-webauthn').'</th>'.$type_th.'<th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></thead><tbody class="wwa-authenticator-list">';
|
||||
$tbody = '<tr><td colspan="'.$loading_colspan.'">'.__('Loading...', 'wp-webauthn').'</td></tr>';
|
||||
$tfoot = '</tbody><tfoot><tr><th>'.__('Identifier', 'wp-webauthn').'</th>'.$type_th.'<th>'._x('Registered', 'time', 'wp-webauthn').'</th><th>'.__('Last used', 'wp-webauthn').'</th><th class="wwa-usernameless-th">'.__('Usernameless', 'wp-webauthn').'</th><th>'.__('Action', 'wp-webauthn').'</th></tr></tfoot></table></div><p class="wwa-authenticator-list-usernameless-tip"></p><p class="wwa-authenticator-list-type-tip"></p>';
|
||||
|
||||
// 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.'<tr><td colspan="5">'.__('You haven\'t logged in yet.', 'wp-webauthn').'</td></tr>'.$tfoot;
|
||||
return $thead.'<tr><td colspan="'.$loading_colspan.'">'.__('You haven\'t logged in yet.', 'wp-webauthn').'</td></tr>'.$tfoot;
|
||||
}else{
|
||||
return '';
|
||||
}
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
<?php
|
||||
if (!defined('ABSPATH')) {
|
||||
exit;
|
||||
}
|
||||
|
||||
$wwa_version = array(
|
||||
'version' => '1.3.4',
|
||||
'commit' => 'b7ef5ce'
|
||||
'version' => '1.4.1',
|
||||
'commit' => 'f5955ea'
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user