App Passwords: Improve validation and sanitization of the application name.

Application names are now required to be unique and cannot contain solely whitespace characters. Additionally, invalid characters are now stripped from the application name using `sanitize_text_field()`.

Props Boniu91, hellofromTonya, engahmeds3ed, xkon, francina.
Fixes #51941.

Built from https://develop.svn.wordpress.org/trunk@50030


git-svn-id: http://core.svn.wordpress.org/trunk@49731 1a063a9b-81f0-0310-95a4-ce76da25c4cd
This commit is contained in:
TimothyBlynJacobs 2021-01-27 19:05:02 +00:00
parent d7e0ddebd1
commit ee710f2c16
7 changed files with 43 additions and 6 deletions

View File

@ -57,7 +57,7 @@
$newAppPassButton.prop( 'disabled', false );
$newAppPassForm.after( tmplNewAppPass( {
name: name,
name: response.name,
password: response.password
} ) );
$( '.new-application-password-notice' ).focus();

View File

@ -1,2 +1,2 @@
/*! This file is auto-generated */
!function(o){var a=o("#application-passwords-section"),i=a.find(".create-application-password"),n=i.find(".input"),t=i.find(".button"),p=a.find(".application-passwords-list-table-wrapper"),r=a.find("tbody"),d=r.find(".no-items"),e=o("#revoke-all-application-passwords"),l=wp.template("new-application-password"),c=wp.template("application-password-row"),u=o("#user_id").val();function w(e,s,a){f(e.responseJSON&&e.responseJSON.message?e.responseJSON.message:a,"error")}function f(e,s){e=o("<div></div>").attr("role","alert").attr("tabindex","-1").addClass("is-dismissible notice notice-"+s).append(o("<p></p>").text(e)).append(o("<button></button>").attr("type","button").addClass("notice-dismiss").append(o("<span></span>").addClass("screen-reader-text").text(wp.i18n.__("Dismiss this notice."))));return i.after(e),e}function v(){o(".notice",a).remove()}t.on("click",function(e){var s,a;e.preventDefault(),t.prop("aria-disabled")||(0!==(s=n.val()).length?(v(),t.prop("aria-disabled",!0).addClass("disabled"),a={name:s},a=wp.hooks.applyFilters("wp_application_passwords_new_password_request",a,u),wp.apiRequest({path:"/wp/v2/users/"+u+"/application-passwords?_locale=user",method:"POST",data:a}).always(function(){t.removeProp("aria-disabled").removeClass("disabled")}).done(function(e){n.val(""),t.prop("disabled",!1),i.after(l({name:s,password:e.password})),o(".new-application-password-notice").focus(),r.prepend(c(e)),p.show(),d.remove(),wp.hooks.doAction("wp_application_passwords_created_password",e,a)}).fail(w)):n.focus())}),r.on("click",".delete",function(e){var s,a;e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke this password? This action cannot be undone."))&&(s=o(this),e=(a=s.closest("tr")).data("uuid"),v(),s.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+u+"/application-passwords/"+e+"?_locale=user",method:"DELETE"}).always(function(){s.prop("disabled",!1)}).done(function(e){e.deleted&&(0===a.siblings().length&&p.hide(),a.remove(),f(wp.i18n.__("Application password revoked."),"success").focus())}).fail(w))}),e.on("click",function(e){var s;e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke all passwords? This action cannot be undone."))&&(s=o(this),v(),s.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+u+"/application-passwords?_locale=user",method:"DELETE"}).always(function(){s.prop("disabled",!1)}).done(function(e){e.deleted&&(r.children().remove(),a.children(".new-application-password").remove(),p.hide(),f(wp.i18n.__("All application passwords revoked."),"success").focus())}).fail(w))}),a.on("click",".notice-dismiss",function(e){e.preventDefault();var s=o(this).parent();s.removeAttr("role"),s.fadeTo(100,0,function(){s.slideUp(100,function(){s.remove(),n.focus()})})}),0===r.children("tr").not(d).length&&p.hide()}(jQuery);
!function(o){var a=o("#application-passwords-section"),i=a.find(".create-application-password"),n=i.find(".input"),t=i.find(".button"),p=a.find(".application-passwords-list-table-wrapper"),r=a.find("tbody"),d=r.find(".no-items"),e=o("#revoke-all-application-passwords"),l=wp.template("new-application-password"),c=wp.template("application-password-row"),u=o("#user_id").val();function w(e,s,a){f(e.responseJSON&&e.responseJSON.message?e.responseJSON.message:a,"error")}function f(e,s){e=o("<div></div>").attr("role","alert").attr("tabindex","-1").addClass("is-dismissible notice notice-"+s).append(o("<p></p>").text(e)).append(o("<button></button>").attr("type","button").addClass("notice-dismiss").append(o("<span></span>").addClass("screen-reader-text").text(wp.i18n.__("Dismiss this notice."))));return i.after(e),e}function v(){o(".notice",a).remove()}t.on("click",function(e){var s;e.preventDefault(),t.prop("aria-disabled")||(0!==(e=n.val()).length?(v(),t.prop("aria-disabled",!0).addClass("disabled"),s={name:e},s=wp.hooks.applyFilters("wp_application_passwords_new_password_request",s,u),wp.apiRequest({path:"/wp/v2/users/"+u+"/application-passwords?_locale=user",method:"POST",data:s}).always(function(){t.removeProp("aria-disabled").removeClass("disabled")}).done(function(e){n.val(""),t.prop("disabled",!1),i.after(l({name:e.name,password:e.password})),o(".new-application-password-notice").focus(),r.prepend(c(e)),p.show(),d.remove(),wp.hooks.doAction("wp_application_passwords_created_password",e,s)}).fail(w)):n.focus())}),r.on("click",".delete",function(e){var s,a;e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke this password? This action cannot be undone."))&&(s=o(this),e=(a=s.closest("tr")).data("uuid"),v(),s.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+u+"/application-passwords/"+e+"?_locale=user",method:"DELETE"}).always(function(){s.prop("disabled",!1)}).done(function(e){e.deleted&&(0===a.siblings().length&&p.hide(),a.remove(),f(wp.i18n.__("Application password revoked."),"success").focus())}).fail(w))}),e.on("click",function(e){var s;e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke all passwords? This action cannot be undone."))&&(s=o(this),v(),s.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+u+"/application-passwords?_locale=user",method:"DELETE"}).always(function(){s.prop("disabled",!1)}).done(function(e){e.deleted&&(r.children().remove(),a.children(".new-application-password").remove(),p.hide(),f(wp.i18n.__("All application passwords revoked."),"success").focus())}).fail(w))}),a.on("click",".notice-dismiss",function(e){e.preventDefault();var s=o(this).parent();s.removeAttr("role"),s.fadeTo(100,0,function(){s.slideUp(100,function(){s.remove(),n.focus()})})}),0===r.children("tr").not(d).length&&p.hide()}(jQuery);

View File

@ -98,7 +98,7 @@
.append( '<p>' + wp.i18n.__( 'Be sure to save this in a safe location. You will not be able to retrieve it.' ) + '</p>' );
// We're using .text() to write the variables to avoid any chance of XSS.
$( 'strong', $notice ).text( name );
$( 'strong', $notice ).text( response.name );
$( 'input', $notice ).val( response.password );
$form.replaceWith( $notice );

View File

@ -1,2 +1,2 @@
/*! This file is auto-generated */
!function(t,n){var s=t("#app_name"),r=t("#approve"),e=t("#reject"),i=s.closest("form"),o={userLogin:n.user_login,successUrl:n.success,rejectUrl:n.reject};r.click(function(e){var p=s.val(),a=t('input[name="app_id"]',i).val();e.preventDefault(),r.prop("aria-disabled")||(0!==p.length?(r.prop("aria-disabled",!0).addClass("disabled"),e={name:p},0<a.length&&(e.app_id=a),e=wp.hooks.applyFilters("wp_application_passwords_approve_app_request",e,o),wp.apiRequest({path:"/wp/v2/users/me/application-passwords?_locale=user",method:"POST",data:e}).done(function(e,a,s){wp.hooks.doAction("wp_application_passwords_approve_app_request_success",e,a,s);var o,s=n.success;s?(o=s+(-1===s.indexOf("?")?"?":"&")+"site_url="+encodeURIComponent(n.site_url)+"&user_login="+encodeURIComponent(n.user_login)+"&password="+encodeURIComponent(e.password),window.location=o):(o=wp.i18n.sprintf('<label for="new-application-password-value">'+wp.i18n.__("Your new password for %s is:")+"</label>","<strong></strong>")+' <input id="new-application-password-value" type="text" class="code" readonly="readonly" value="" />',o=t("<div></div>").attr("role","alert").attr("tabindex",-1).addClass("notice notice-success notice-alt").append(t("<p></p>").addClass("application-password-display").html(o)).append("<p>"+wp.i18n.__("Be sure to save this in a safe location. You will not be able to retrieve it.")+"</p>"),t("strong",o).text(p),t("input",o).val(e.password),i.replaceWith(o),o.focus())}).fail(function(e,a,s){var o=s,p=null;e.responseJSON&&(p=e.responseJSON).message&&(o=p.message);o=t("<div></div>").attr("role","alert").addClass("notice notice-error").append(t("<p></p>").text(o));t("h1").after(o),r.removeProp("aria-disabled",!1).removeClass("disabled"),wp.hooks.doAction("wp_application_passwords_approve_app_request_error",p,a,s,e)})):s.focus())}),e.click(function(e){e.preventDefault(),wp.hooks.doAction("wp_application_passwords_reject_app",o),window.location=n.reject}),i.on("submit",function(e){e.preventDefault()})}(jQuery,authApp);
!function(t,p){var o=t("#app_name"),n=t("#approve"),e=t("#reject"),r=o.closest("form"),i={userLogin:p.user_login,successUrl:p.success,rejectUrl:p.reject};n.click(function(e){var a=o.val(),s=t('input[name="app_id"]',r).val();e.preventDefault(),n.prop("aria-disabled")||(0!==a.length?(n.prop("aria-disabled",!0).addClass("disabled"),a={name:a},0<s.length&&(a.app_id=s),a=wp.hooks.applyFilters("wp_application_passwords_approve_app_request",a,i),wp.apiRequest({path:"/wp/v2/users/me/application-passwords?_locale=user",method:"POST",data:a}).done(function(e,a,s){wp.hooks.doAction("wp_application_passwords_approve_app_request_success",e,a,s);var o,s=p.success;s?(o=s+(-1===s.indexOf("?")?"?":"&")+"site_url="+encodeURIComponent(p.site_url)+"&user_login="+encodeURIComponent(p.user_login)+"&password="+encodeURIComponent(e.password),window.location=o):(o=wp.i18n.sprintf('<label for="new-application-password-value">'+wp.i18n.__("Your new password for %s is:")+"</label>","<strong></strong>")+' <input id="new-application-password-value" type="text" class="code" readonly="readonly" value="" />',o=t("<div></div>").attr("role","alert").attr("tabindex",-1).addClass("notice notice-success notice-alt").append(t("<p></p>").addClass("application-password-display").html(o)).append("<p>"+wp.i18n.__("Be sure to save this in a safe location. You will not be able to retrieve it.")+"</p>"),t("strong",o).text(e.name),t("input",o).val(e.password),r.replaceWith(o),o.focus())}).fail(function(e,a,s){var o=s,p=null;e.responseJSON&&(p=e.responseJSON).message&&(o=p.message);o=t("<div></div>").attr("role","alert").addClass("notice notice-error").append(t("<p></p>").text(o));t("h1").after(o),n.removeProp("aria-disabled",!1).removeClass("disabled"),wp.hooks.doAction("wp_application_passwords_approve_app_request_error",p,a,s,e)})):o.focus())}),e.click(function(e){e.preventDefault(),wp.hooks.doAction("wp_application_passwords_reject_app",i),window.location=p.reject}),r.on("submit",function(e){e.preventDefault()})}(jQuery,authApp);

View File

@ -58,6 +58,7 @@ class WP_Application_Passwords {
* Creates a new application password.
*
* @since 5.6.0
* @since 5.7.0 Returns WP_Error if application name already exists.
*
* @param int $user_id User ID.
* @param array $args Information about the application password.
@ -65,8 +66,16 @@ class WP_Application_Passwords {
* A WP_Error instance is returned on error.
*/
public static function create_new_application_password( $user_id, $args = array() ) {
if ( ! empty( $args['name'] ) ) {
$args['name'] = sanitize_text_field( $args['name'] );
}
if ( empty( $args['name'] ) ) {
return new WP_Error( 'application_password_empty_name', __( 'An application name is required to create an application password.' ) );
return new WP_Error( 'application_password_empty_name', __( 'An application name is required to create an application password.' ), array( 'status' => 400 ) );
}
if ( self::application_name_exists_for_user( $user_id, $args['name'] ) ) {
return new WP_Error( 'application_password_duplicate_name', __( 'Each application name should be unique.' ), array( 'status' => 409 ) );
}
$new_password = wp_generate_password( static::PW_LENGTH, false );
@ -162,6 +171,28 @@ class WP_Application_Passwords {
return null;
}
/**
* Check if application name exists before for this user.
*
* @since 5.7.0
*
* @param int $user_id User ID.
* @param string $name Application name.
*
* @return bool Provided application name exists or not.
*/
public static function application_name_exists_for_user( $user_id, $name ) {
$passwords = static::get_user_application_passwords( $user_id );
foreach ( $passwords as $password ) {
if ( strtolower( $password['name'] ) === strtolower( $name ) ) {
return true;
}
}
return false;
}
/**
* Updates an application password.
*
@ -180,6 +211,10 @@ class WP_Application_Passwords {
continue;
}
if ( ! empty( $update['name'] ) ) {
$update['name'] = sanitize_text_field( $update['name'] );
}
$save = false;
if ( ! empty( $update['name'] ) && $item['name'] !== $update['name'] ) {

View File

@ -631,6 +631,8 @@ class WP_REST_Application_Passwords_Controller extends WP_REST_Controller {
'type' => 'string',
'required' => true,
'context' => array( 'view', 'edit', 'embed' ),
'minLength' => 1,
'pattern' => '.*\S.*',
),
'password' => array(
'description' => __( 'The generated password. Only available after adding an application.' ),

View File

@ -13,7 +13,7 @@
*
* @global string $wp_version
*/
$wp_version = '5.7-alpha-50029';
$wp_version = '5.7-alpha-50030';
/**
* Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema.