From d5ebe12f1177b6a2548db662721d0b6a0129333b Mon Sep 17 00:00:00 2001 From: TimothyBlynJacobs Date: Thu, 8 Oct 2020 22:14:06 +0000 Subject: [PATCH] REST API: Introduce Application Passwords for API authentication. In WordPress 4.4 the REST API was first introduced. A few releases later in WordPress 4.7, the Content API endpoints were added, paving the way for Gutenberg and countless in-site experiences. In the intervening years, numerous plugins have built on top of the REST API. Many developers shared a common frustration, the lack of external authentication to the REST API. This commit introduces Application Passwords to allow users to connect to external applications to their WordPress website. Users can generate individual passwords for each application, allowing for easy revocation and activity monitoring. An authorization flow is introduced to make the connection flow simple for users and application developers. Application Passwords uses Basic Authentication, and by default is only available over an SSL connection. Props georgestephanis, kasparsd, timothyblynjacobs, afercia, akkspro, andraganescu, arippberger, aristath, austyfrosty, ayesh, batmoo, bradyvercher, brianhenryie, helen, ipstenu, jeffmatson, jeffpaul, joostdevalk, joshlevinson, kadamwhite, kjbenk, koke, michael-arestad, Otto42, pekz0r, salzano, spacedmonkey, valendesigns. Fixes #42790. Built from https://develop.svn.wordpress.org/trunk@49109 git-svn-id: http://core.svn.wordpress.org/trunk@48871 1a063a9b-81f0-0310-95a4-ce76da25c4cd --- wp-admin/authorize-application.php | 230 ++++++ wp-admin/css/forms-rtl.css | 7 +- wp-admin/css/forms-rtl.min.css | 2 +- wp-admin/css/forms.css | 7 +- wp-admin/css/forms.min.css | 2 +- ...ss-wp-application-passwords-list-table.php | 248 +++++++ wp-admin/includes/list-table.php | 1 + wp-admin/includes/user.php | 58 ++ wp-admin/js/application-passwords.js | 196 ++++++ wp-admin/js/application-passwords.min.js | 2 + wp-admin/js/auth-app.js | 152 ++++ wp-admin/js/auth-app.min.js | 2 + wp-admin/user-edit.php | 62 ++ .../class-wp-application-passwords.php | 318 +++++++++ wp-includes/class-wp-rewrite.php | 1 + wp-includes/default-filters.php | 5 + wp-includes/load.php | 41 ++ wp-includes/rest-api.php | 80 ++- wp-includes/rest-api/class-wp-rest-server.php | 19 + ...-rest-application-passwords-controller.php | 656 ++++++++++++++++++ wp-includes/script-loader.php | 6 + wp-includes/user.php | 226 ++++++ wp-includes/version.php | 2 +- wp-login.php | 13 + wp-settings.php | 2 + 25 files changed, 2332 insertions(+), 6 deletions(-) create mode 100644 wp-admin/authorize-application.php create mode 100644 wp-admin/includes/class-wp-application-passwords-list-table.php create mode 100644 wp-admin/js/application-passwords.js create mode 100644 wp-admin/js/application-passwords.min.js create mode 100644 wp-admin/js/auth-app.js create mode 100644 wp-admin/js/auth-app.min.js create mode 100644 wp-includes/class-wp-application-passwords.php create mode 100644 wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php diff --git a/wp-admin/authorize-application.php b/wp-admin/authorize-application.php new file mode 100644 index 0000000000..dd60a349f6 --- /dev/null +++ b/wp-admin/authorize-application.php @@ -0,0 +1,230 @@ + $app_name ) ); + + if ( is_wp_error( $created ) ) { + $error = $created; + } else { + list( $new_password ) = $created; + + if ( $success_url ) { + $redirect = add_query_arg( + array( + 'username' => urlencode( wp_get_current_user()->user_login ), + 'password' => urlencode( $new_password ), + ), + $success_url + ); + } + } + } + + if ( $redirect ) { + // Explicitly not using wp_safe_redirect b/c sends to arbitrary domain. + wp_redirect( $redirect ); + exit; + } +} + +$title = __( 'Authorize Application' ); + +$app_name = ! empty( $_REQUEST['app_name'] ) ? $_REQUEST['app_name'] : ''; +$success_url = ! empty( $_REQUEST['success_url'] ) ? $_REQUEST['success_url'] : null; +$reject_url = ! empty( $_REQUEST['reject_url'] ) ? $_REQUEST['reject_url'] : $success_url; +$user = wp_get_current_user(); + +$request = compact( 'app_name', 'success_url', 'reject_url' ); +$is_valid = wp_is_authorize_application_password_request_valid( $request, $user ); + +if ( is_wp_error( $is_valid ) ) { + wp_die( + __( 'The Authorize Application request is not allowed.' ) . ' ' . implode( ' ', $is_valid->get_error_messages() ), + __( 'Cannot Authorize Application' ) + ); +} + +if ( ! wp_is_application_passwords_available_for_user( $user ) ) { + if ( wp_is_application_passwords_available() ) { + $message = __( 'Application passwords are not enabled for your account. Please contact the site administrator for assistance.' ); + } else { + $message = __( 'Application passwords are not enabled.' ); + } + + wp_die( + $message, + __( 'Cannot Authorize Application' ), + array( + 'link_text' => __( 'Go Back' ), + 'link_url' => $reject_url ? add_query_arg( 'error', 'disabled', $reject_url ) : admin_url(), + ) + ); +} + +wp_enqueue_script( 'auth-app' ); +wp_localize_script( + 'auth-app', + 'authApp', + array( + 'user_login' => $user->user_login, + 'success' => $success_url, + 'reject' => $reject_url ? $reject_url : admin_url(), + ) +); + +require_once ABSPATH . 'wp-admin/admin-header.php'; + +?> +
+

+ + +

get_error_message(); ?>

+ + +
+

+ +

+ ' . esc_html( $app_name ) . '' ); + ?> +

+ +

+ + + +
+

+ ' . esc_html( $app_name ) . '', + '' . esc_html( WP_Application_Passwords::chunk_password( $new_password ) ) . '' + ); + ?> +

+
+ + + +
+ + + + + + + + + + +

+
+ ' . esc_html( + add_query_arg( + array( + 'username' => $user->user_login, + 'password' => '[------]', + ), + $success_url + ) + ) . '' + ); + } else { + _e( 'You will be given a password to manually enter into the application in question.' ); + } + ?> + +

+ +

+
+ ' . esc_html( + add_query_arg( + array( + 'success' => 'false', + ), + $reject_url + ) + ) . '' + ); + } else { + _e( 'You will be returned to the WordPress Dashboard, and no changes will be made.' ); + } + ?> + +

+
+ +
+
+input[type=checkbox]{margin-top:0}.wp-admin p label input[type=checkbox]{margin-top:-4px}.wp-admin p label input[type=radio]{margin-top:-2px}input[type=radio]{border-radius:50%;margin-left:.25rem;line-height:.71428571}input[type=checkbox]:checked::before,input[type=radio]:checked::before{float:right;display:inline-block;vertical-align:middle;width:1rem;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}input[type=checkbox]:checked::before{content:url(data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.83%204.89l1.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%27%20fill%3D%27%231e8cbe%27%2F%3E%3C%2Fsvg%3E);margin:-.1875rem -.25rem 0 0;height:1.3125rem;width:1.3125rem}input[type=radio]:checked::before{content:"";border-radius:50%;width:.5rem;height:.5rem;margin:.1875rem;background-color:#1e8cbe;line-height:1.14285714}@-moz-document url-prefix(){.form-table input.tog,input[type=checkbox],input[type=radio]{margin-bottom:-1px}}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-decoration{display:none}.wp-admin input[type=file]{padding:3px 0;cursor:pointer}input.readonly,input[readonly],textarea.readonly,textarea[readonly]{background-color:#eee}::-webkit-input-placeholder{color:#72777c}::-moz-placeholder{color:#72777c;opacity:1}:-ms-input-placeholder{color:#72777c}.form-invalid input,.form-invalid input:focus,.form-invalid select,.form-invalid select:focus{border-color:#dc3232!important;box-shadow:0 0 2px rgba(204,0,0,.8)}.form-table .form-required.form-invalid td:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin-right:-25px;vertical-align:middle}.form-table .form-required.user-pass1-wrap.form-invalid td:after{content:""}.form-table .form-required.user-pass1-wrap.form-invalid .password-input-wrapper:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin:0 -29px 0 6px;vertical-align:middle}.form-input-tip{color:#666}input.disabled,input:disabled,select.disabled,select:disabled,textarea.disabled,textarea:disabled{background:rgba(255,255,255,.5);border-color:rgba(222,222,222,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5)}input[type=file].disabled,input[type=file]:disabled,input[type=range].disabled,input[type=range]:disabled{background:0 0;box-shadow:none;cursor:default}input[type=checkbox].disabled,input[type=checkbox].disabled:checked:before,input[type=checkbox]:disabled,input[type=checkbox]:disabled:checked:before,input[type=radio].disabled,input[type=radio].disabled:checked:before,input[type=radio]:disabled,input[type=radio]:disabled:checked:before{opacity:.7}.wp-core-ui select{font-size:14px;line-height:2;color:#32373c;border-color:#7e8993;box-shadow:none;border-radius:3px;padding:0 8px 0 24px;min-height:30px;max-width:25rem;-webkit-appearance:none;background:#fff url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E') no-repeat left 5px top 55%;background-size:16px 16px;cursor:pointer;vertical-align:middle}.wp-core-ui select:hover{color:#007cba}.wp-core-ui select:focus{border-color:#007cba;color:#016087;box-shadow:0 0 0 1px #007cba}.wp-core-ui select:active{border-color:#999;box-shadow:none}.wp-core-ui select.disabled,.wp-core-ui select:disabled{color:#a0a5aa;border-color:#ddd;background-color:#f7f7f7;background-image:url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23a0a5aa%22%2F%3E%3C%2Fsvg%3E');box-shadow:none;text-shadow:0 1px 0 #fff;cursor:default;transform:none}.wp-core-ui select:-moz-focusring{color:transparent;text-shadow:0 0 0 #016087}.wp-core-ui select::-ms-value{background:0 0;color:#555}.wp-core-ui select:hover::-ms-value{color:#007cba}.wp-core-ui select:focus::-ms-value{color:#016087}.wp-core-ui select.disabled::-ms-value,.wp-core-ui select:disabled::-ms-value{color:#a0a5aa}.wp-core-ui select::-ms-expand{display:none}.wp-admin .button-cancel{display:inline-block;min-height:28px;padding:0 5px;line-height:2}.meta-box-sortables select{max-width:100%}.meta-box-sortables input{vertical-align:middle}.misc-pub-post-status select{margin-top:0}.wp-core-ui select[multiple]{height:auto;padding-left:8px;background:#fff}.submit{padding:1.5em 0;margin:5px 0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border:none}form p.submit a.cancel:hover{text-decoration:none}p.submit{text-align:right;max-width:100%;margin-top:20px;padding-top:10px}.textright p.submit{border:none;text-align:left}table.form-table+input+input+p.submit,table.form-table+input+p.submit,table.form-table+p.submit{border-top:none;padding-top:0}#major-publishing-actions input,#minor-publishing-actions .preview,#minor-publishing-actions input{text-align:center}input.all-options,textarea.all-options{width:250px}input.large-text,textarea.large-text{width:99%}.regular-text{width:25em}input.small-text{width:50px;padding:0 6px}label input.small-text{margin-top:-4px}input[type=number].small-text{width:65px;padding-left:0}input.tiny-text{width:35px}input[type=number].tiny-text{width:45px;padding-left:0}#doaction,#doaction2,#post-query-submit{margin:0 0 0 8px}.tablenav .actions select{float:right;margin-left:6px;max-width:12.5rem}#timezone_string option{margin-right:1em}.wp-cancel-pw>.dashicons,.wp-hide-pw>.dashicons{position:relative;top:3px;width:1.25rem;height:1.25rem;top:.25rem;font-size:20px}.wp-cancel-pw .dashicons-no{display:none}#your-profile label+a,label{vertical-align:middle}#your-profile label+a,fieldset label{vertical-align:middle}.options-media-php [for*="_size_"]{min-width:10em;vertical-align:baseline}.options-media-php .small-text[name*="_size_"]{margin:0 0 1em}#misc-publishing-actions label{vertical-align:baseline}#pass-strength-result{background-color:#eee;border:1px solid #ddd;color:#23282d;margin:-1px 1px 5px;padding:3px 5px;text-align:center;width:25em;box-sizing:border-box;opacity:0}#pass-strength-result.short{background-color:#f1adad;border-color:#e35b5b;opacity:1}#pass-strength-result.bad{background-color:#fbc5a9;border-color:#f78b53;opacity:1}#pass-strength-result.good{background-color:#ffe399;border-color:#ffc733;opacity:1}#pass-strength-result.strong{background-color:#c1e1b9;border-color:#83c373;opacity:1}#pass1-text.short,#pass1.short{border-color:#e35b5b}#pass1-text.bad,#pass1.bad{border-color:#f78b53}#pass1-text.good,#pass1.good{border-color:#ffc733}#pass1-text.strong,#pass1.strong{border-color:#83c373}.pw-weak{display:none}.indicator-hint{padding-top:8px}.wp-pwd [type=password],.wp-pwd [type=text]{margin-bottom:0;min-height:30px}.wp-pwd input::-ms-reveal{display:none}#pass1-text,.show-password #pass1{display:none}#pass1-text::-ms-clear{display:none}.show-password #pass1-text{display:inline-block}p.search-box{float:left;margin:0}.network-admin.themes-php p.search-box{clear:right}.search-box input[name="s"],.tablenav .search-plugins input[name="s"],.tagsdiv .newtag{float:right;margin:0 0 0 4px}.js.plugins-php .search-box .wp-filter-search{margin:0;width:280px}input[type=email].ui-autocomplete-loading,input[type=text].ui-autocomplete-loading{background-image:url(../images/loading.gif);background-repeat:no-repeat;background-position:left center;visibility:visible}input.ui-autocomplete-input.open{border-bottom-color:transparent}ul#add-to-blog-users{margin:0 14px 0 0}.ui-autocomplete{padding:0;margin:0;list-style:none;position:absolute;z-index:10000;border:1px solid #5b9dd9;box-shadow:0 1px 2px rgba(30,140,190,.8);background-color:#fff}.ui-autocomplete li{margin-bottom:0;padding:4px 10px;white-space:nowrap;text-align:right;cursor:pointer}.ui-autocomplete .ui-state-focus{background-color:#ddd}.wp-tags-autocomplete .ui-state-focus{background-color:#0073aa;color:#fff}.form-table{border-collapse:collapse;margin-top:.5em;width:100%;clear:both}.form-table,.form-table td,.form-table td p,.form-table th{font-size:14px}.form-table td{margin-bottom:9px;padding:15px 10px;line-height:1.3;vertical-align:middle}.form-table th,.form-wrap label{color:#23282d;font-weight:400;text-shadow:none;vertical-align:baseline}.form-table th{vertical-align:top;text-align:right;padding:20px 0 20px 10px;width:200px;line-height:1.3;font-weight:600}.form-table .td-full,.form-table th.th-full{width:auto;padding:20px 0 20px 10px;font-weight:400}.form-table td p{margin-top:4px;margin-bottom:0}.form-table .date-time-doc{margin-top:1em}.form-table p.timezone-info{margin:1em 0}.form-table td fieldset label{margin:.35em 0 .5em!important;display:inline-block}.form-table td fieldset p label{margin-top:0!important}.form-table td fieldset label,.form-table td fieldset li,.form-table td fieldset p{line-height:1.4}.form-table input.tog,.form-table input[type=radio]{margin-top:-4px;margin-left:4px;float:none}.form-table .pre{padding:8px;margin:0}table.form-table td .updated{font-size:13px}table.form-table td .updated p{font-size:13px;margin:.3em 0}#profile-page .form-table textarea{width:500px;margin-bottom:6px}#profile-page .form-table #rich_editing{margin-left:5px}#your-profile legend{font-size:22px}#display_name{width:15em}#adduser .form-field input,#createuser .form-field input{width:25em}.color-option{display:inline-block;width:24%;padding:5px 15px 15px;box-sizing:border-box;margin-bottom:3px}.color-option.selected,.color-option:hover{background:#ddd}.color-palette{width:100%;border-spacing:0;border-collapse:collapse}.color-palette td{height:20px;padding:0;border:none}.color-option{cursor:pointer}.tool-box .title{margin:8px 0;font-size:18px;font-weight:400;line-height:24px}.label-responsive{vertical-align:middle}#export-filters p{margin:0 0 1em}#export-filters p.submit{margin:7px 0 5px}.card{position:relative;margin-top:20px;padding:.7em 2em 1em;min-width:255px;max-width:520px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);background:#fff;box-sizing:border-box}.pressthis h4{margin:2em 0 1em}.pressthis textarea{width:100%;font-size:1em}#pressthis-code-wrap{overflow:auto}.pressthis-bookmarklet-wrapper{margin:20px 0 8px;vertical-align:top;position:relative;z-index:1}.pressthis-bookmarklet,.pressthis-bookmarklet:active,.pressthis-bookmarklet:focus,.pressthis-bookmarklet:hover{display:inline-block;position:relative;cursor:move;color:#32373c;background:#e5e5e5;border-radius:5px;border:1px solid #b4b9be;font-style:normal;line-height:16px;font-size:14px;text-decoration:none}.pressthis-bookmarklet:active{outline:0}.pressthis-bookmarklet:after{content:"";width:70%;height:55%;z-index:-1;position:absolute;left:10px;bottom:9px;background:0 0;transform:skew(-20deg) rotate(-6deg);box-shadow:0 10px 8px rgba(0,0,0,.6)}.pressthis-bookmarklet:hover:after{transform:skew(-20deg) rotate(-9deg);box-shadow:0 10px 8px rgba(0,0,0,.7)}.pressthis-bookmarklet span{display:inline-block;margin:0 0 0;padding:0 9px 8px 12px}.pressthis-bookmarklet span:before{color:#72777c;font:normal 20px/1 dashicons;content:"\f157";position:relative;display:inline-block;top:4px;margin-left:4px}.pressthis-js-toggle{margin-right:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle.button.button{margin-right:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle .dashicons{margin:5px 7px 6px 8px;color:#555d66}.timezone-info code{white-space:nowrap}.defaultavatarpicker .avatar{margin:2px 0;vertical-align:middle}.options-general-php .date-time-text{display:inline-block;min-width:10em}.options-general-php input.small-text{width:56px;margin:-2px 0}.options-general-php .spinner{float:none;margin:-3px 3px 0}.options-general-php .language-install-spinner,.settings-php .language-install-spinner{display:inline-block;float:none;margin:-3px 5px 0;vertical-align:middle}.form-table.permalink-structure .available-structure-tags li{float:right;margin-left:5px}.setup-php textarea{max-width:100%}.form-field #site-address{max-width:25em}.form-field #domain{max-width:22em}.form-field #admin-email,.form-field #blog_last_updated,.form-field #blog_registered,.form-field #path,.form-field #site-title{max-width:25em}.form-field #path{margin-bottom:5px}#search-sites,#search-users{max-width:60%}.request-filesystem-credentials-dialog{display:none;visibility:visible}.request-filesystem-credentials-dialog .notification-dialog{top:10%;max-height:85%}.request-filesystem-credentials-dialog-content{margin:25px}#request-filesystem-credentials-title{font-size:1.3em;margin:1em 0}.request-filesystem-credentials-form legend{font-size:1em;padding:1.33em 0;font-weight:600}.request-filesystem-credentials-form input[type=password],.request-filesystem-credentials-form input[type=text]{display:block}.request-filesystem-credentials-dialog input[type=password],.request-filesystem-credentials-dialog input[type=text]{width:100%}.request-filesystem-credentials-form .field-title{font-weight:600}.request-filesystem-credentials-dialog label[for=hostname],.request-filesystem-credentials-dialog label[for=private_key],.request-filesystem-credentials-dialog label[for=public_key]{display:block;margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:right;width:48%}.request-filesystem-credentials-dialog .ftp-password{margin-right:4%}.request-filesystem-credentials-dialog .request-filesystem-credentials-action-buttons{text-align:left}.request-filesystem-credentials-dialog label[for=ftp]{margin-left:10px}.request-filesystem-credentials-dialog #auth-keys-desc{margin-bottom:0}#request-filesystem-credentials-dialog .button:not(:last-child){margin-left:10px}#request-filesystem-credentials-form .cancel-button{display:none}#request-filesystem-credentials-dialog .cancel-button{display:inline}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:none;width:auto}.request-filesystem-credentials-dialog .ftp-username{margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password{margin:0}.request-filesystem-credentials-dialog .ftp-password em{color:#888}.request-filesystem-credentials-dialog label{display:block;line-height:1.5;margin-bottom:1em}.request-filesystem-credentials-form legend{padding-bottom:0}.request-filesystem-credentials-form #ssh-keys legend{font-size:1.3em}.request-filesystem-credentials-form .notice{margin:0 0 20px 0;clear:both}.tools-privacy-policy-page form{margin-bottom:1.3em}.tools-privacy-policy-page input.button,.tools-privacy-policy-page select{margin:0 6px 0 1px}.tools-privacy-edit{margin:1.5em 0}.tools-privacy-policy-page span{line-height:2}.privacy_requests .column-email{width:40%}.privacy_requests .column-type{text-align:center}.privacy_requests tfoot td:first-child,.privacy_requests thead td:first-child{border-right:4px solid #fff}.privacy_requests tbody th{border-right:4px solid #fff;background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests .row-actions{color:#72777c}.privacy_requests .row-actions.processing{position:static}.privacy_requests tbody .has-request-results th{box-shadow:none}.privacy_requests tbody .request-results th .notice{margin:0 0 5px}.privacy_requests tbody td{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests tbody .has-request-results td{box-shadow:none}.privacy_requests .next_steps .button{word-wrap:break-word;white-space:normal}.privacy_requests .status-request-confirmed td,.privacy_requests .status-request-confirmed th{background-color:#f7fcfe;border-right-color:#00a0d2}.privacy_requests .status-request-failed td,.privacy_requests .status-request-failed th{background-color:#fef7f1;border-right-color:#d64d21}.privacy_requests .export_personal_data_failed a{vertical-align:baseline}.status-label{font-weight:600}.status-label.status-request-pending{font-weight:400;font-style:italic;color:#6c7781}.status-label.status-request-failed{color:#a00;font-weight:600}.wp-privacy-request-form{clear:both}.wp-privacy-request-form-field{margin:1.5em 0}.wp-privacy-request-form label{font-weight:600;line-height:1.5;padding-bottom:.5em;display:block}.wp-privacy-request-form input{margin:0}.email-personal-data::before{display:inline-block;font:normal 20px/1 dashicons;margin:3px -2px 0 5px;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;vertical-align:top}.email-personal-data--sending::before{color:#f56e28;content:"\f463";animation:rotation 2s infinite linear}.email-personal-data--sent::before{color:#79ba49;content:"\f147"}@media screen and (max-width:782px){textarea{-webkit-appearance:none}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:3px 10px;min-height:40px}::-webkit-datetime-edit{line-height:1.875}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox],input[type=checkbox]{-webkit-appearance:none}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox]{margin-bottom:8px}.widefat tfoot td input[type=checkbox]:before,.widefat th input[type=checkbox]:before,.widefat thead td input[type=checkbox]:before,input[type=checkbox]:checked:before{width:1.875rem;height:1.875rem;margin:-.1875rem -.3125rem}input[type=checkbox],input[type=radio]{height:1.5625rem;width:1.5625rem}.wp-admin p input[type=checkbox],.wp-admin p input[type=radio]{margin-top:-.1875rem}input[type=radio]:checked:before{vertical-align:middle;width:.5625rem;height:.5625rem;margin:.4375rem;line-height:.76190476}.wp-upload-form input[type=submit]{margin-top:10px}.wp-admin .form-table select,.wp-core-ui select{min-height:40px;font-size:16px;line-height:1.625;padding:5px 8px 5px 24px}.wp-admin .button-cancel{margin-bottom:0;padding:2px 0;font-size:14px;vertical-align:middle}#adduser .form-field input,#createuser .form-field input{width:100%}.form-table{box-sizing:border-box}.form-table td,.form-table th,.label-responsive{display:block;width:auto;vertical-align:middle}.label-responsive{margin:.5em 0}.export-filters li{margin-bottom:0}.form-table .color-palette td{display:table-cell;width:15px}.form-table table.color-palette{margin-left:10px}input,textarea{font-size:16px}#profile-page .form-table textarea,.form-table span.description,.form-table td input[type=email],.form-table td input[type=password],.form-table td input[type=text],.form-table td select,.form-table td textarea{width:100%;display:block;max-width:none;box-sizing:border-box}.form-table .form-required.form-invalid td:after{float:left;margin:-30px 0 0 3px}.form-table input[type=text].small-text,input[type=number].small-text,input[type=password].small-text,input[type=search].small-text,input[type=text].small-text{width:auto;max-width:4.375em;display:inline;padding:3px 6px;margin:0 3px}#pass-strength-result{width:100%;box-sizing:border-box;padding:8px}p.search-box{float:none;position:absolute;bottom:0;width:98%;height:90px;margin-bottom:20px}p.search-box input[name="s"]{float:none;width:100%;margin-bottom:10px;vertical-align:middle}p.search-box input[type=submit]{margin-bottom:10px}.form-table span.description{display:inline;padding:4px 0 0;line-height:1.4;font-size:14px}.form-table th{padding:10px 0 0 0;border-bottom:0}.form-table td{margin-bottom:0;padding:4px 0 6px 0}.form-table.permalink-structure td code{margin-right:32px;display:inline-block}.form-table.permalink-structure td input[type=text]{margin-right:32px;margin-top:4px;width:96%}.form-table input.regular-text{width:100%}.form-table label{font-size:14px}.background-position-control .button-group>label{font-size:0}.form-table fieldset label{display:block}#local-time,#utc-time{display:block;float:none;margin-top:.5em}.form-field #domain{max-width:none}.wp-pwd{position:relative}#profile-page .form-table #pass1{padding-left:90px}.wp-pwd button.button{background:0 0;border:1px solid transparent;box-shadow:none;line-height:2;margin:0;padding:5px 9px;position:absolute;left:0;top:0;width:2.375rem;height:2.375rem;min-width:40px;min-height:40px}.wp-pwd button.wp-hide-pw{left:2.5rem}.wp-pwd button.button:focus,.wp-pwd button.button:hover{background:0 0}.wp-pwd button.button:active{background:0 0;box-shadow:none;transform:none}.wp-pwd .button .text{display:none}.wp-pwd [type=password],.wp-pwd [type=text]{line-height:2;padding-left:5rem}.wp-cancel-pw .dashicons-no{display:inline-block}.options-general-php input[type=text].small-text{max-width:6.25em;margin:0}.tools-privacy-policy-page form.wp-create-privacy-page{margin-bottom:1em}.tools-privacy-policy-page input#set-page,.tools-privacy-policy-page select{margin:10px 0 0}.tools-privacy-policy-page .wp-create-privacy-page span{display:block;margin-bottom:1em}.tools-privacy-policy-page .wp-create-privacy-page .button{margin-right:0}.wp-list-table.privacy_requests tr:not(.inline-edit-row):not(.no-items) td.column-primary:not(.check-column){display:table-cell}.wp-list-table.privacy_requests.widefat th input,.wp-list-table.privacy_requests.widefat thead td input{margin-right:5px}.wp-privacy-request-form-field input[type=text]{width:100%;margin-bottom:10px;vertical-align:middle}.regular-text{max-width:100%}}@media only screen and (max-width:768px){.form-field input[type=email],.form-field input[type=password],.form-field input[type=text],.form-field select,.form-field textarea{width:99%}.form-wrap .form-field{padding:0}}@media only screen and (max-height:480px),screen and (max-width:450px){.file-editor-warning .notification-dialog,.request-filesystem-credentials-dialog .notification-dialog{width:100%;height:100%;max-height:100%;position:fixed;top:0;margin:0;right:0}}@media screen and (max-width:600px){.color-option{width:49%}}@media only screen and (max-width:320px){.options-general-php .date-time-text.date-time-custom-text{min-width:0;margin-left:.5em}}@keyframes rotation{0%{transform:rotate(0)}100%{transform:rotate(-359deg)}} \ No newline at end of file +button,input,select,textarea{box-sizing:border-box;font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea{font-size:14px}textarea{overflow:auto;padding:2px 6px;line-height:1.42857143;resize:vertical}label{cursor:pointer}input,select{margin:0 1px}textarea.code{padding:4px 6px 1px 6px}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{box-shadow:0 0 0 transparent;border-radius:4px;border:1px solid #7e8993;background-color:#fff;color:#32373c}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{padding:0 8px;line-height:2;min-height:30px}::-webkit-datetime-edit{line-height:1.85714286}input[type=checkbox]:focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=radio]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#007cba;box-shadow:0 0 0 1px #007cba;outline:2px solid transparent}input[type=email],input[type=url]{direction:ltr}input[type=checkbox],input[type=radio]{border:1px solid #7e8993;border-radius:4px;background:#fff;color:#555;clear:none;cursor:pointer;display:inline-block;line-height:0;height:1rem;margin:-.25rem 0 0 .25rem;outline:0;padding:0!important;text-align:center;vertical-align:middle;width:1rem;min-width:1rem;-webkit-appearance:none;box-shadow:inset 0 1px 2px rgba(0,0,0,.1);transition:.05s border-color ease-in-out}input[type=radio]:checked+label:before{color:#82878c}.wp-core-ui input[type=reset]:active,.wp-core-ui input[type=reset]:hover{color:#006799}.wp-admin p input[type=checkbox],.wp-admin p input[type=radio],td>input[type=checkbox]{margin-top:0}.wp-admin p label input[type=checkbox]{margin-top:-4px}.wp-admin p label input[type=radio]{margin-top:-2px}input[type=radio]{border-radius:50%;margin-left:.25rem;line-height:.71428571}input[type=checkbox]:checked::before,input[type=radio]:checked::before{float:right;display:inline-block;vertical-align:middle;width:1rem;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}input[type=checkbox]:checked::before{content:url(data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.83%204.89l1.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%27%20fill%3D%27%231e8cbe%27%2F%3E%3C%2Fsvg%3E);margin:-.1875rem -.25rem 0 0;height:1.3125rem;width:1.3125rem}input[type=radio]:checked::before{content:"";border-radius:50%;width:.5rem;height:.5rem;margin:.1875rem;background-color:#1e8cbe;line-height:1.14285714}@-moz-document url-prefix(){.form-table input.tog,input[type=checkbox],input[type=radio]{margin-bottom:-1px}}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-decoration{display:none}.wp-admin input[type=file]{padding:3px 0;cursor:pointer}input.readonly,input[readonly],textarea.readonly,textarea[readonly]{background-color:#eee}::-webkit-input-placeholder{color:#72777c}::-moz-placeholder{color:#72777c;opacity:1}:-ms-input-placeholder{color:#72777c}.form-invalid input,.form-invalid input:focus,.form-invalid select,.form-invalid select:focus{border-color:#dc3232!important;box-shadow:0 0 2px rgba(204,0,0,.8)}.form-table .form-required.form-invalid td:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin-right:-25px;vertical-align:middle}.form-table .form-required.user-pass1-wrap.form-invalid td:after{content:""}.form-table .form-required.user-pass1-wrap.form-invalid .password-input-wrapper:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin:0 -29px 0 6px;vertical-align:middle}.form-input-tip{color:#666}input.disabled,input:disabled,select.disabled,select:disabled,textarea.disabled,textarea:disabled{background:rgba(255,255,255,.5);border-color:rgba(222,222,222,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5)}input[type=file].disabled,input[type=file]:disabled,input[type=range].disabled,input[type=range]:disabled{background:0 0;box-shadow:none;cursor:default}input[type=checkbox].disabled,input[type=checkbox].disabled:checked:before,input[type=checkbox]:disabled,input[type=checkbox]:disabled:checked:before,input[type=radio].disabled,input[type=radio].disabled:checked:before,input[type=radio]:disabled,input[type=radio]:disabled:checked:before{opacity:.7}.wp-core-ui select{font-size:14px;line-height:2;color:#32373c;border-color:#7e8993;box-shadow:none;border-radius:3px;padding:0 8px 0 24px;min-height:30px;max-width:25rem;-webkit-appearance:none;background:#fff url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E') no-repeat left 5px top 55%;background-size:16px 16px;cursor:pointer;vertical-align:middle}.wp-core-ui select:hover{color:#007cba}.wp-core-ui select:focus{border-color:#007cba;color:#016087;box-shadow:0 0 0 1px #007cba}.wp-core-ui select:active{border-color:#999;box-shadow:none}.wp-core-ui select.disabled,.wp-core-ui select:disabled{color:#a0a5aa;border-color:#ddd;background-color:#f7f7f7;background-image:url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23a0a5aa%22%2F%3E%3C%2Fsvg%3E');box-shadow:none;text-shadow:0 1px 0 #fff;cursor:default;transform:none}.wp-core-ui select:-moz-focusring{color:transparent;text-shadow:0 0 0 #016087}.wp-core-ui select::-ms-value{background:0 0;color:#555}.wp-core-ui select:hover::-ms-value{color:#007cba}.wp-core-ui select:focus::-ms-value{color:#016087}.wp-core-ui select.disabled::-ms-value,.wp-core-ui select:disabled::-ms-value{color:#a0a5aa}.wp-core-ui select::-ms-expand{display:none}.wp-admin .button-cancel{display:inline-block;min-height:28px;padding:0 5px;line-height:2}.meta-box-sortables select{max-width:100%}.meta-box-sortables input{vertical-align:middle}.misc-pub-post-status select{margin-top:0}.wp-core-ui select[multiple]{height:auto;padding-left:8px;background:#fff}.submit{padding:1.5em 0;margin:5px 0;border-bottom-right-radius:3px;border-bottom-left-radius:3px;border:none}form p.submit a.cancel:hover{text-decoration:none}p.submit{text-align:right;max-width:100%;margin-top:20px;padding-top:10px}.textright p.submit{border:none;text-align:left}table.form-table+input+input+p.submit,table.form-table+input+p.submit,table.form-table+p.submit{border-top:none;padding-top:0}#major-publishing-actions input,#minor-publishing-actions .preview,#minor-publishing-actions input{text-align:center}input.all-options,textarea.all-options{width:250px}input.large-text,textarea.large-text{width:99%}.regular-text{width:25em}input.small-text{width:50px;padding:0 6px}label input.small-text{margin-top:-4px}input[type=number].small-text{width:65px;padding-left:0}input.tiny-text{width:35px}input[type=number].tiny-text{width:45px;padding-left:0}#doaction,#doaction2,#post-query-submit{margin:0 0 0 8px}.tablenav .actions select{float:right;margin-left:6px;max-width:12.5rem}#timezone_string option{margin-right:1em}.wp-cancel-pw>.dashicons,.wp-hide-pw>.dashicons{position:relative;top:3px;width:1.25rem;height:1.25rem;top:.25rem;font-size:20px}.wp-cancel-pw .dashicons-no{display:none}#your-profile label+a,label{vertical-align:middle}#your-profile label+a,fieldset label{vertical-align:middle}.options-media-php [for*="_size_"]{min-width:10em;vertical-align:baseline}.options-media-php .small-text[name*="_size_"]{margin:0 0 1em}#misc-publishing-actions label{vertical-align:baseline}#pass-strength-result{background-color:#eee;border:1px solid #ddd;color:#23282d;margin:-1px 1px 5px;padding:3px 5px;text-align:center;width:25em;box-sizing:border-box;opacity:0}#pass-strength-result.short{background-color:#f1adad;border-color:#e35b5b;opacity:1}#pass-strength-result.bad{background-color:#fbc5a9;border-color:#f78b53;opacity:1}#pass-strength-result.good{background-color:#ffe399;border-color:#ffc733;opacity:1}#pass-strength-result.strong{background-color:#c1e1b9;border-color:#83c373;opacity:1}#pass1-text.short,#pass1.short{border-color:#e35b5b}#pass1-text.bad,#pass1.bad{border-color:#f78b53}#pass1-text.good,#pass1.good{border-color:#ffc733}#pass1-text.strong,#pass1.strong{border-color:#83c373}.pw-weak{display:none}.indicator-hint{padding-top:8px}.wp-pwd [type=password],.wp-pwd [type=text]{margin-bottom:0;min-height:30px}.wp-pwd input::-ms-reveal{display:none}#pass1-text,.show-password #pass1{display:none}#pass1-text::-ms-clear{display:none}.show-password #pass1-text{display:inline-block}p.search-box{float:left;margin:0}.network-admin.themes-php p.search-box{clear:right}.search-box input[name="s"],.tablenav .search-plugins input[name="s"],.tagsdiv .newtag{float:right;margin:0 0 0 4px}.js.plugins-php .search-box .wp-filter-search{margin:0;width:280px}input[type=email].ui-autocomplete-loading,input[type=text].ui-autocomplete-loading{background-image:url(../images/loading.gif);background-repeat:no-repeat;background-position:left center;visibility:visible}input.ui-autocomplete-input.open{border-bottom-color:transparent}ul#add-to-blog-users{margin:0 14px 0 0}.ui-autocomplete{padding:0;margin:0;list-style:none;position:absolute;z-index:10000;border:1px solid #5b9dd9;box-shadow:0 1px 2px rgba(30,140,190,.8);background-color:#fff}.ui-autocomplete li{margin-bottom:0;padding:4px 10px;white-space:nowrap;text-align:right;cursor:pointer}.ui-autocomplete .ui-state-focus{background-color:#ddd}.wp-tags-autocomplete .ui-state-focus{background-color:#0073aa;color:#fff}.form-table{border-collapse:collapse;margin-top:.5em;width:100%;clear:both}.form-table,.form-table td,.form-table td p,.form-table th{font-size:14px}.form-table td{margin-bottom:9px;padding:15px 10px;line-height:1.3;vertical-align:middle}.form-table th,.form-wrap label{color:#23282d;font-weight:400;text-shadow:none;vertical-align:baseline}.form-table th{vertical-align:top;text-align:right;padding:20px 0 20px 10px;width:200px;line-height:1.3;font-weight:600}.form-table .td-full,.form-table th.th-full{width:auto;padding:20px 0 20px 10px;font-weight:400}.form-table td p{margin-top:4px;margin-bottom:0}.form-table .date-time-doc{margin-top:1em}.form-table p.timezone-info{margin:1em 0}.form-table td fieldset label{margin:.35em 0 .5em!important;display:inline-block}.form-table td fieldset p label{margin-top:0!important}.form-table td fieldset label,.form-table td fieldset li,.form-table td fieldset p{line-height:1.4}.form-table input.tog,.form-table input[type=radio]{margin-top:-4px;margin-left:4px;float:none}.form-table .pre{padding:8px;margin:0}table.form-table td .updated{font-size:13px}table.form-table td .updated p{font-size:13px;margin:.3em 0}#profile-page .form-table textarea{width:500px;margin-bottom:6px}#profile-page .form-table #rich_editing{margin-left:5px}#your-profile legend{font-size:22px}#display_name{width:15em}#adduser .form-field input,#createuser .form-field input{width:25em}.color-option{display:inline-block;width:24%;padding:5px 15px 15px;box-sizing:border-box;margin-bottom:3px}.color-option.selected,.color-option:hover{background:#ddd}.color-palette{width:100%;border-spacing:0;border-collapse:collapse}.color-palette td{height:20px;padding:0;border:none}.color-option{cursor:pointer}.new-application-password-notice.notice{margin-top:20px;margin-bottom:0}.tool-box .title{margin:8px 0;font-size:18px;font-weight:400;line-height:24px}.label-responsive{vertical-align:middle}#export-filters p{margin:0 0 1em}#export-filters p.submit{margin:7px 0 5px}.card{position:relative;margin-top:20px;padding:.7em 2em 1em;min-width:255px;max-width:520px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);background:#fff;box-sizing:border-box}.pressthis h4{margin:2em 0 1em}.pressthis textarea{width:100%;font-size:1em}#pressthis-code-wrap{overflow:auto}.pressthis-bookmarklet-wrapper{margin:20px 0 8px;vertical-align:top;position:relative;z-index:1}.pressthis-bookmarklet,.pressthis-bookmarklet:active,.pressthis-bookmarklet:focus,.pressthis-bookmarklet:hover{display:inline-block;position:relative;cursor:move;color:#32373c;background:#e5e5e5;border-radius:5px;border:1px solid #b4b9be;font-style:normal;line-height:16px;font-size:14px;text-decoration:none}.pressthis-bookmarklet:active{outline:0}.pressthis-bookmarklet:after{content:"";width:70%;height:55%;z-index:-1;position:absolute;left:10px;bottom:9px;background:0 0;transform:skew(-20deg) rotate(-6deg);box-shadow:0 10px 8px rgba(0,0,0,.6)}.pressthis-bookmarklet:hover:after{transform:skew(-20deg) rotate(-9deg);box-shadow:0 10px 8px rgba(0,0,0,.7)}.pressthis-bookmarklet span{display:inline-block;margin:0 0 0;padding:0 9px 8px 12px}.pressthis-bookmarklet span:before{color:#72777c;font:normal 20px/1 dashicons;content:"\f157";position:relative;display:inline-block;top:4px;margin-left:4px}.pressthis-js-toggle{margin-right:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle.button.button{margin-right:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle .dashicons{margin:5px 7px 6px 8px;color:#555d66}.timezone-info code{white-space:nowrap}.defaultavatarpicker .avatar{margin:2px 0;vertical-align:middle}.options-general-php .date-time-text{display:inline-block;min-width:10em}.options-general-php input.small-text{width:56px;margin:-2px 0}.options-general-php .spinner{float:none;margin:-3px 3px 0}.options-general-php .language-install-spinner,.settings-php .language-install-spinner{display:inline-block;float:none;margin:-3px 5px 0;vertical-align:middle}.form-table.permalink-structure .available-structure-tags li{float:right;margin-left:5px}.setup-php textarea{max-width:100%}.form-field #site-address{max-width:25em}.form-field #domain{max-width:22em}.form-field #admin-email,.form-field #blog_last_updated,.form-field #blog_registered,.form-field #path,.form-field #site-title{max-width:25em}.form-field #path{margin-bottom:5px}#search-sites,#search-users{max-width:60%}.request-filesystem-credentials-dialog{display:none;visibility:visible}.request-filesystem-credentials-dialog .notification-dialog{top:10%;max-height:85%}.request-filesystem-credentials-dialog-content{margin:25px}#request-filesystem-credentials-title{font-size:1.3em;margin:1em 0}.request-filesystem-credentials-form legend{font-size:1em;padding:1.33em 0;font-weight:600}.request-filesystem-credentials-form input[type=password],.request-filesystem-credentials-form input[type=text]{display:block}.request-filesystem-credentials-dialog input[type=password],.request-filesystem-credentials-dialog input[type=text]{width:100%}.request-filesystem-credentials-form .field-title{font-weight:600}.request-filesystem-credentials-dialog label[for=hostname],.request-filesystem-credentials-dialog label[for=private_key],.request-filesystem-credentials-dialog label[for=public_key]{display:block;margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:right;width:48%}.request-filesystem-credentials-dialog .ftp-password{margin-right:4%}.request-filesystem-credentials-dialog .request-filesystem-credentials-action-buttons{text-align:left}.request-filesystem-credentials-dialog label[for=ftp]{margin-left:10px}.request-filesystem-credentials-dialog #auth-keys-desc{margin-bottom:0}#request-filesystem-credentials-dialog .button:not(:last-child){margin-left:10px}#request-filesystem-credentials-form .cancel-button{display:none}#request-filesystem-credentials-dialog .cancel-button{display:inline}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:none;width:auto}.request-filesystem-credentials-dialog .ftp-username{margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password{margin:0}.request-filesystem-credentials-dialog .ftp-password em{color:#888}.request-filesystem-credentials-dialog label{display:block;line-height:1.5;margin-bottom:1em}.request-filesystem-credentials-form legend{padding-bottom:0}.request-filesystem-credentials-form #ssh-keys legend{font-size:1.3em}.request-filesystem-credentials-form .notice{margin:0 0 20px 0;clear:both}.tools-privacy-policy-page form{margin-bottom:1.3em}.tools-privacy-policy-page input.button,.tools-privacy-policy-page select{margin:0 6px 0 1px}.tools-privacy-edit{margin:1.5em 0}.tools-privacy-policy-page span{line-height:2}.privacy_requests .column-email{width:40%}.privacy_requests .column-type{text-align:center}.privacy_requests tfoot td:first-child,.privacy_requests thead td:first-child{border-right:4px solid #fff}.privacy_requests tbody th{border-right:4px solid #fff;background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests .row-actions{color:#72777c}.privacy_requests .row-actions.processing{position:static}.privacy_requests tbody .has-request-results th{box-shadow:none}.privacy_requests tbody .request-results th .notice{margin:0 0 5px}.privacy_requests tbody td{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests tbody .has-request-results td{box-shadow:none}.privacy_requests .next_steps .button{word-wrap:break-word;white-space:normal}.privacy_requests .status-request-confirmed td,.privacy_requests .status-request-confirmed th{background-color:#f7fcfe;border-right-color:#00a0d2}.privacy_requests .status-request-failed td,.privacy_requests .status-request-failed th{background-color:#fef7f1;border-right-color:#d64d21}.privacy_requests .export_personal_data_failed a{vertical-align:baseline}.status-label{font-weight:600}.status-label.status-request-pending{font-weight:400;font-style:italic;color:#6c7781}.status-label.status-request-failed{color:#a00;font-weight:600}.wp-privacy-request-form{clear:both}.wp-privacy-request-form-field{margin:1.5em 0}.wp-privacy-request-form label{font-weight:600;line-height:1.5;padding-bottom:.5em;display:block}.wp-privacy-request-form input{margin:0}.email-personal-data::before{display:inline-block;font:normal 20px/1 dashicons;margin:3px -2px 0 5px;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;vertical-align:top}.email-personal-data--sending::before{color:#f56e28;content:"\f463";animation:rotation 2s infinite linear}.email-personal-data--sent::before{color:#79ba49;content:"\f147"}@media screen and (max-width:782px){textarea{-webkit-appearance:none}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:3px 10px;min-height:40px}::-webkit-datetime-edit{line-height:1.875}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox],input[type=checkbox]{-webkit-appearance:none}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox]{margin-bottom:8px}.widefat tfoot td input[type=checkbox]:before,.widefat th input[type=checkbox]:before,.widefat thead td input[type=checkbox]:before,input[type=checkbox]:checked:before{width:1.875rem;height:1.875rem;margin:-.1875rem -.3125rem}input[type=checkbox],input[type=radio]{height:1.5625rem;width:1.5625rem}.wp-admin p input[type=checkbox],.wp-admin p input[type=radio]{margin-top:-.1875rem}input[type=radio]:checked:before{vertical-align:middle;width:.5625rem;height:.5625rem;margin:.4375rem;line-height:.76190476}.wp-upload-form input[type=submit]{margin-top:10px}.wp-admin .form-table select,.wp-core-ui select{min-height:40px;font-size:16px;line-height:1.625;padding:5px 8px 5px 24px}.wp-admin .button-cancel{margin-bottom:0;padding:2px 0;font-size:14px;vertical-align:middle}#adduser .form-field input,#createuser .form-field input{width:100%}.form-table{box-sizing:border-box}.form-table td,.form-table th,.label-responsive{display:block;width:auto;vertical-align:middle}.label-responsive{margin:.5em 0}.export-filters li{margin-bottom:0}.form-table .color-palette td{display:table-cell;width:15px}.form-table table.color-palette{margin-left:10px}input,textarea{font-size:16px}#profile-page .form-table textarea,.form-table span.description,.form-table td input[type=email],.form-table td input[type=password],.form-table td input[type=text],.form-table td select,.form-table td textarea{width:100%;display:block;max-width:none;box-sizing:border-box}.form-table .form-required.form-invalid td:after{float:left;margin:-30px 0 0 3px}.form-table input[type=text].small-text,input[type=number].small-text,input[type=password].small-text,input[type=search].small-text,input[type=text].small-text{width:auto;max-width:4.375em;display:inline;padding:3px 6px;margin:0 3px}#pass-strength-result{width:100%;box-sizing:border-box;padding:8px}p.search-box{float:none;position:absolute;bottom:0;width:98%;height:90px;margin-bottom:20px}p.search-box input[name="s"]{float:none;width:100%;margin-bottom:10px;vertical-align:middle}p.search-box input[type=submit]{margin-bottom:10px}.form-table span.description{display:inline;padding:4px 0 0;line-height:1.4;font-size:14px}.form-table th{padding:10px 0 0 0;border-bottom:0}.form-table td{margin-bottom:0;padding:4px 0 6px 0}.form-table.permalink-structure td code{margin-right:32px;display:inline-block}.form-table.permalink-structure td input[type=text]{margin-right:32px;margin-top:4px;width:96%}.form-table input.regular-text{width:100%}.form-table label{font-size:14px}.background-position-control .button-group>label{font-size:0}.form-table fieldset label{display:block}#local-time,#utc-time{display:block;float:none;margin-top:.5em}.form-field #domain{max-width:none}.wp-pwd{position:relative}#profile-page .form-table #pass1{padding-left:90px}.wp-pwd button.button{background:0 0;border:1px solid transparent;box-shadow:none;line-height:2;margin:0;padding:5px 9px;position:absolute;left:0;top:0;width:2.375rem;height:2.375rem;min-width:40px;min-height:40px}.wp-pwd button.wp-hide-pw{left:2.5rem}.wp-pwd button.button:focus,.wp-pwd button.button:hover{background:0 0}.wp-pwd button.button:active{background:0 0;box-shadow:none;transform:none}.wp-pwd .button .text{display:none}.wp-pwd [type=password],.wp-pwd [type=text]{line-height:2;padding-left:5rem}.wp-cancel-pw .dashicons-no{display:inline-block}.options-general-php input[type=text].small-text{max-width:6.25em;margin:0}.tools-privacy-policy-page form.wp-create-privacy-page{margin-bottom:1em}.tools-privacy-policy-page input#set-page,.tools-privacy-policy-page select{margin:10px 0 0}.tools-privacy-policy-page .wp-create-privacy-page span{display:block;margin-bottom:1em}.tools-privacy-policy-page .wp-create-privacy-page .button{margin-right:0}.wp-list-table.privacy_requests tr:not(.inline-edit-row):not(.no-items) td.column-primary:not(.check-column){display:table-cell}.wp-list-table.privacy_requests.widefat th input,.wp-list-table.privacy_requests.widefat thead td input{margin-right:5px}.wp-privacy-request-form-field input[type=text]{width:100%;margin-bottom:10px;vertical-align:middle}.regular-text{max-width:100%}}@media only screen and (max-width:768px){.form-field input[type=email],.form-field input[type=password],.form-field input[type=text],.form-field select,.form-field textarea{width:99%}.form-wrap .form-field{padding:0}}@media only screen and (max-height:480px),screen and (max-width:450px){.file-editor-warning .notification-dialog,.request-filesystem-credentials-dialog .notification-dialog{width:100%;height:100%;max-height:100%;position:fixed;top:0;margin:0;right:0}}@media screen and (max-width:600px){.color-option{width:49%}}@media only screen and (max-width:320px){.options-general-php .date-time-text.date-time-custom-text{min-width:0;margin-left:.5em}}@keyframes rotation{0%{transform:rotate(0)}100%{transform:rotate(-359deg)}} \ No newline at end of file diff --git a/wp-admin/css/forms.css b/wp-admin/css/forms.css index 0aa63f1f52..2f03173491 100644 --- a/wp-admin/css/forms.css +++ b/wp-admin/css/forms.css @@ -756,7 +756,7 @@ ul#add-to-blog-users { display: inline-block; } -.form-table td fieldset p label { +.form-table td fieldset p label { margin-top: 0 !important; } @@ -841,6 +841,11 @@ table.form-table td .updated p { cursor: pointer; } +.new-application-password-notice.notice { + margin-top: 20px; + margin-bottom: 0; +} + /*------------------------------------------------------------------------------ 19.0 - Tools ------------------------------------------------------------------------------*/ diff --git a/wp-admin/css/forms.min.css b/wp-admin/css/forms.min.css index 6b17f677d1..4e1b839cfc 100644 --- a/wp-admin/css/forms.min.css +++ b/wp-admin/css/forms.min.css @@ -1,2 +1,2 @@ /*! This file is auto-generated */ -button,input,select,textarea{box-sizing:border-box;font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea{font-size:14px}textarea{overflow:auto;padding:2px 6px;line-height:1.42857143;resize:vertical}label{cursor:pointer}input,select{margin:0 1px}textarea.code{padding:4px 6px 1px 6px}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{box-shadow:0 0 0 transparent;border-radius:4px;border:1px solid #7e8993;background-color:#fff;color:#32373c}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{padding:0 8px;line-height:2;min-height:30px}::-webkit-datetime-edit{line-height:1.85714286}input[type=checkbox]:focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=radio]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#007cba;box-shadow:0 0 0 1px #007cba;outline:2px solid transparent}input[type=email],input[type=url]{direction:ltr}input[type=checkbox],input[type=radio]{border:1px solid #7e8993;border-radius:4px;background:#fff;color:#555;clear:none;cursor:pointer;display:inline-block;line-height:0;height:1rem;margin:-.25rem .25rem 0 0;outline:0;padding:0!important;text-align:center;vertical-align:middle;width:1rem;min-width:1rem;-webkit-appearance:none;box-shadow:inset 0 1px 2px rgba(0,0,0,.1);transition:.05s border-color ease-in-out}input[type=radio]:checked+label:before{color:#82878c}.wp-core-ui input[type=reset]:active,.wp-core-ui input[type=reset]:hover{color:#006799}.wp-admin p input[type=checkbox],.wp-admin p input[type=radio],td>input[type=checkbox]{margin-top:0}.wp-admin p label input[type=checkbox]{margin-top:-4px}.wp-admin p label input[type=radio]{margin-top:-2px}input[type=radio]{border-radius:50%;margin-right:.25rem;line-height:.71428571}input[type=checkbox]:checked::before,input[type=radio]:checked::before{float:left;display:inline-block;vertical-align:middle;width:1rem;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}input[type=checkbox]:checked::before{content:url(data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.83%204.89l1.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%27%20fill%3D%27%231e8cbe%27%2F%3E%3C%2Fsvg%3E);margin:-.1875rem 0 0 -.25rem;height:1.3125rem;width:1.3125rem}input[type=radio]:checked::before{content:"";border-radius:50%;width:.5rem;height:.5rem;margin:.1875rem;background-color:#1e8cbe;line-height:1.14285714}@-moz-document url-prefix(){.form-table input.tog,input[type=checkbox],input[type=radio]{margin-bottom:-1px}}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-decoration{display:none}.wp-admin input[type=file]{padding:3px 0;cursor:pointer}input.readonly,input[readonly],textarea.readonly,textarea[readonly]{background-color:#eee}::-webkit-input-placeholder{color:#72777c}::-moz-placeholder{color:#72777c;opacity:1}:-ms-input-placeholder{color:#72777c}.form-invalid input,.form-invalid input:focus,.form-invalid select,.form-invalid select:focus{border-color:#dc3232!important;box-shadow:0 0 2px rgba(204,0,0,.8)}.form-table .form-required.form-invalid td:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin-left:-25px;vertical-align:middle}.form-table .form-required.user-pass1-wrap.form-invalid td:after{content:""}.form-table .form-required.user-pass1-wrap.form-invalid .password-input-wrapper:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin:0 6px 0 -29px;vertical-align:middle}.form-input-tip{color:#666}input.disabled,input:disabled,select.disabled,select:disabled,textarea.disabled,textarea:disabled{background:rgba(255,255,255,.5);border-color:rgba(222,222,222,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5)}input[type=file].disabled,input[type=file]:disabled,input[type=range].disabled,input[type=range]:disabled{background:0 0;box-shadow:none;cursor:default}input[type=checkbox].disabled,input[type=checkbox].disabled:checked:before,input[type=checkbox]:disabled,input[type=checkbox]:disabled:checked:before,input[type=radio].disabled,input[type=radio].disabled:checked:before,input[type=radio]:disabled,input[type=radio]:disabled:checked:before{opacity:.7}.wp-core-ui select{font-size:14px;line-height:2;color:#32373c;border-color:#7e8993;box-shadow:none;border-radius:3px;padding:0 24px 0 8px;min-height:30px;max-width:25rem;-webkit-appearance:none;background:#fff url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E') no-repeat right 5px top 55%;background-size:16px 16px;cursor:pointer;vertical-align:middle}.wp-core-ui select:hover{color:#007cba}.wp-core-ui select:focus{border-color:#007cba;color:#016087;box-shadow:0 0 0 1px #007cba}.wp-core-ui select:active{border-color:#999;box-shadow:none}.wp-core-ui select.disabled,.wp-core-ui select:disabled{color:#a0a5aa;border-color:#ddd;background-color:#f7f7f7;background-image:url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23a0a5aa%22%2F%3E%3C%2Fsvg%3E');box-shadow:none;text-shadow:0 1px 0 #fff;cursor:default;transform:none}.wp-core-ui select:-moz-focusring{color:transparent;text-shadow:0 0 0 #016087}.wp-core-ui select::-ms-value{background:0 0;color:#555}.wp-core-ui select:hover::-ms-value{color:#007cba}.wp-core-ui select:focus::-ms-value{color:#016087}.wp-core-ui select.disabled::-ms-value,.wp-core-ui select:disabled::-ms-value{color:#a0a5aa}.wp-core-ui select::-ms-expand{display:none}.wp-admin .button-cancel{display:inline-block;min-height:28px;padding:0 5px;line-height:2}.meta-box-sortables select{max-width:100%}.meta-box-sortables input{vertical-align:middle}.misc-pub-post-status select{margin-top:0}.wp-core-ui select[multiple]{height:auto;padding-right:8px;background:#fff}.submit{padding:1.5em 0;margin:5px 0;border-bottom-left-radius:3px;border-bottom-right-radius:3px;border:none}form p.submit a.cancel:hover{text-decoration:none}p.submit{text-align:left;max-width:100%;margin-top:20px;padding-top:10px}.textright p.submit{border:none;text-align:right}table.form-table+input+input+p.submit,table.form-table+input+p.submit,table.form-table+p.submit{border-top:none;padding-top:0}#major-publishing-actions input,#minor-publishing-actions .preview,#minor-publishing-actions input{text-align:center}input.all-options,textarea.all-options{width:250px}input.large-text,textarea.large-text{width:99%}.regular-text{width:25em}input.small-text{width:50px;padding:0 6px}label input.small-text{margin-top:-4px}input[type=number].small-text{width:65px;padding-right:0}input.tiny-text{width:35px}input[type=number].tiny-text{width:45px;padding-right:0}#doaction,#doaction2,#post-query-submit{margin:0 8px 0 0}.tablenav .actions select{float:left;margin-right:6px;max-width:12.5rem}#timezone_string option{margin-left:1em}.wp-cancel-pw>.dashicons,.wp-hide-pw>.dashicons{position:relative;top:3px;width:1.25rem;height:1.25rem;top:.25rem;font-size:20px}.wp-cancel-pw .dashicons-no{display:none}#your-profile label+a,label{vertical-align:middle}#your-profile label+a,fieldset label{vertical-align:middle}.options-media-php [for*="_size_"]{min-width:10em;vertical-align:baseline}.options-media-php .small-text[name*="_size_"]{margin:0 0 1em}#misc-publishing-actions label{vertical-align:baseline}#pass-strength-result{background-color:#eee;border:1px solid #ddd;color:#23282d;margin:-1px 1px 5px;padding:3px 5px;text-align:center;width:25em;box-sizing:border-box;opacity:0}#pass-strength-result.short{background-color:#f1adad;border-color:#e35b5b;opacity:1}#pass-strength-result.bad{background-color:#fbc5a9;border-color:#f78b53;opacity:1}#pass-strength-result.good{background-color:#ffe399;border-color:#ffc733;opacity:1}#pass-strength-result.strong{background-color:#c1e1b9;border-color:#83c373;opacity:1}#pass1-text.short,#pass1.short{border-color:#e35b5b}#pass1-text.bad,#pass1.bad{border-color:#f78b53}#pass1-text.good,#pass1.good{border-color:#ffc733}#pass1-text.strong,#pass1.strong{border-color:#83c373}.pw-weak{display:none}.indicator-hint{padding-top:8px}.wp-pwd [type=password],.wp-pwd [type=text]{margin-bottom:0;min-height:30px}.wp-pwd input::-ms-reveal{display:none}#pass1-text,.show-password #pass1{display:none}#pass1-text::-ms-clear{display:none}.show-password #pass1-text{display:inline-block}p.search-box{float:right;margin:0}.network-admin.themes-php p.search-box{clear:left}.search-box input[name="s"],.tablenav .search-plugins input[name="s"],.tagsdiv .newtag{float:left;margin:0 4px 0 0}.js.plugins-php .search-box .wp-filter-search{margin:0;width:280px}input[type=email].ui-autocomplete-loading,input[type=text].ui-autocomplete-loading{background-image:url(../images/loading.gif);background-repeat:no-repeat;background-position:right center;visibility:visible}input.ui-autocomplete-input.open{border-bottom-color:transparent}ul#add-to-blog-users{margin:0 0 0 14px}.ui-autocomplete{padding:0;margin:0;list-style:none;position:absolute;z-index:10000;border:1px solid #5b9dd9;box-shadow:0 1px 2px rgba(30,140,190,.8);background-color:#fff}.ui-autocomplete li{margin-bottom:0;padding:4px 10px;white-space:nowrap;text-align:left;cursor:pointer}.ui-autocomplete .ui-state-focus{background-color:#ddd}.wp-tags-autocomplete .ui-state-focus{background-color:#0073aa;color:#fff}.form-table{border-collapse:collapse;margin-top:.5em;width:100%;clear:both}.form-table,.form-table td,.form-table td p,.form-table th{font-size:14px}.form-table td{margin-bottom:9px;padding:15px 10px;line-height:1.3;vertical-align:middle}.form-table th,.form-wrap label{color:#23282d;font-weight:400;text-shadow:none;vertical-align:baseline}.form-table th{vertical-align:top;text-align:left;padding:20px 10px 20px 0;width:200px;line-height:1.3;font-weight:600}.form-table .td-full,.form-table th.th-full{width:auto;padding:20px 10px 20px 0;font-weight:400}.form-table td p{margin-top:4px;margin-bottom:0}.form-table .date-time-doc{margin-top:1em}.form-table p.timezone-info{margin:1em 0}.form-table td fieldset label{margin:.35em 0 .5em!important;display:inline-block}.form-table td fieldset p label{margin-top:0!important}.form-table td fieldset label,.form-table td fieldset li,.form-table td fieldset p{line-height:1.4}.form-table input.tog,.form-table input[type=radio]{margin-top:-4px;margin-right:4px;float:none}.form-table .pre{padding:8px;margin:0}table.form-table td .updated{font-size:13px}table.form-table td .updated p{font-size:13px;margin:.3em 0}#profile-page .form-table textarea{width:500px;margin-bottom:6px}#profile-page .form-table #rich_editing{margin-right:5px}#your-profile legend{font-size:22px}#display_name{width:15em}#adduser .form-field input,#createuser .form-field input{width:25em}.color-option{display:inline-block;width:24%;padding:5px 15px 15px;box-sizing:border-box;margin-bottom:3px}.color-option.selected,.color-option:hover{background:#ddd}.color-palette{width:100%;border-spacing:0;border-collapse:collapse}.color-palette td{height:20px;padding:0;border:none}.color-option{cursor:pointer}.tool-box .title{margin:8px 0;font-size:18px;font-weight:400;line-height:24px}.label-responsive{vertical-align:middle}#export-filters p{margin:0 0 1em}#export-filters p.submit{margin:7px 0 5px}.card{position:relative;margin-top:20px;padding:.7em 2em 1em;min-width:255px;max-width:520px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);background:#fff;box-sizing:border-box}.pressthis h4{margin:2em 0 1em}.pressthis textarea{width:100%;font-size:1em}#pressthis-code-wrap{overflow:auto}.pressthis-bookmarklet-wrapper{margin:20px 0 8px;vertical-align:top;position:relative;z-index:1}.pressthis-bookmarklet,.pressthis-bookmarklet:active,.pressthis-bookmarklet:focus,.pressthis-bookmarklet:hover{display:inline-block;position:relative;cursor:move;color:#32373c;background:#e5e5e5;border-radius:5px;border:1px solid #b4b9be;font-style:normal;line-height:16px;font-size:14px;text-decoration:none}.pressthis-bookmarklet:active{outline:0}.pressthis-bookmarklet:after{content:"";width:70%;height:55%;z-index:-1;position:absolute;right:10px;bottom:9px;background:0 0;transform:skew(20deg) rotate(6deg);box-shadow:0 10px 8px rgba(0,0,0,.6)}.pressthis-bookmarklet:hover:after{transform:skew(20deg) rotate(9deg);box-shadow:0 10px 8px rgba(0,0,0,.7)}.pressthis-bookmarklet span{display:inline-block;margin:0 0 0;padding:0 12px 8px 9px}.pressthis-bookmarklet span:before{color:#72777c;font:normal 20px/1 dashicons;content:"\f157";position:relative;display:inline-block;top:4px;margin-right:4px}.pressthis-js-toggle{margin-left:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle.button.button{margin-left:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle .dashicons{margin:5px 8px 6px 7px;color:#555d66}.timezone-info code{white-space:nowrap}.defaultavatarpicker .avatar{margin:2px 0;vertical-align:middle}.options-general-php .date-time-text{display:inline-block;min-width:10em}.options-general-php input.small-text{width:56px;margin:-2px 0}.options-general-php .spinner{float:none;margin:-3px 3px 0}.options-general-php .language-install-spinner,.settings-php .language-install-spinner{display:inline-block;float:none;margin:-3px 5px 0;vertical-align:middle}.form-table.permalink-structure .available-structure-tags li{float:left;margin-right:5px}.setup-php textarea{max-width:100%}.form-field #site-address{max-width:25em}.form-field #domain{max-width:22em}.form-field #admin-email,.form-field #blog_last_updated,.form-field #blog_registered,.form-field #path,.form-field #site-title{max-width:25em}.form-field #path{margin-bottom:5px}#search-sites,#search-users{max-width:60%}.request-filesystem-credentials-dialog{display:none;visibility:visible}.request-filesystem-credentials-dialog .notification-dialog{top:10%;max-height:85%}.request-filesystem-credentials-dialog-content{margin:25px}#request-filesystem-credentials-title{font-size:1.3em;margin:1em 0}.request-filesystem-credentials-form legend{font-size:1em;padding:1.33em 0;font-weight:600}.request-filesystem-credentials-form input[type=password],.request-filesystem-credentials-form input[type=text]{display:block}.request-filesystem-credentials-dialog input[type=password],.request-filesystem-credentials-dialog input[type=text]{width:100%}.request-filesystem-credentials-form .field-title{font-weight:600}.request-filesystem-credentials-dialog label[for=hostname],.request-filesystem-credentials-dialog label[for=private_key],.request-filesystem-credentials-dialog label[for=public_key]{display:block;margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:left;width:48%}.request-filesystem-credentials-dialog .ftp-password{margin-left:4%}.request-filesystem-credentials-dialog .request-filesystem-credentials-action-buttons{text-align:right}.request-filesystem-credentials-dialog label[for=ftp]{margin-right:10px}.request-filesystem-credentials-dialog #auth-keys-desc{margin-bottom:0}#request-filesystem-credentials-dialog .button:not(:last-child){margin-right:10px}#request-filesystem-credentials-form .cancel-button{display:none}#request-filesystem-credentials-dialog .cancel-button{display:inline}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:none;width:auto}.request-filesystem-credentials-dialog .ftp-username{margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password{margin:0}.request-filesystem-credentials-dialog .ftp-password em{color:#888}.request-filesystem-credentials-dialog label{display:block;line-height:1.5;margin-bottom:1em}.request-filesystem-credentials-form legend{padding-bottom:0}.request-filesystem-credentials-form #ssh-keys legend{font-size:1.3em}.request-filesystem-credentials-form .notice{margin:0 0 20px 0;clear:both}.tools-privacy-policy-page form{margin-bottom:1.3em}.tools-privacy-policy-page input.button,.tools-privacy-policy-page select{margin:0 1px 0 6px}.tools-privacy-edit{margin:1.5em 0}.tools-privacy-policy-page span{line-height:2}.privacy_requests .column-email{width:40%}.privacy_requests .column-type{text-align:center}.privacy_requests tfoot td:first-child,.privacy_requests thead td:first-child{border-left:4px solid #fff}.privacy_requests tbody th{border-left:4px solid #fff;background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests .row-actions{color:#72777c}.privacy_requests .row-actions.processing{position:static}.privacy_requests tbody .has-request-results th{box-shadow:none}.privacy_requests tbody .request-results th .notice{margin:0 0 5px}.privacy_requests tbody td{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests tbody .has-request-results td{box-shadow:none}.privacy_requests .next_steps .button{word-wrap:break-word;white-space:normal}.privacy_requests .status-request-confirmed td,.privacy_requests .status-request-confirmed th{background-color:#f7fcfe;border-left-color:#00a0d2}.privacy_requests .status-request-failed td,.privacy_requests .status-request-failed th{background-color:#fef7f1;border-left-color:#d64d21}.privacy_requests .export_personal_data_failed a{vertical-align:baseline}.status-label{font-weight:600}.status-label.status-request-pending{font-weight:400;font-style:italic;color:#6c7781}.status-label.status-request-failed{color:#a00;font-weight:600}.wp-privacy-request-form{clear:both}.wp-privacy-request-form-field{margin:1.5em 0}.wp-privacy-request-form label{font-weight:600;line-height:1.5;padding-bottom:.5em;display:block}.wp-privacy-request-form input{margin:0}.email-personal-data::before{display:inline-block;font:normal 20px/1 dashicons;margin:3px 5px 0 -2px;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;vertical-align:top}.email-personal-data--sending::before{color:#f56e28;content:"\f463";animation:rotation 2s infinite linear}.email-personal-data--sent::before{color:#79ba49;content:"\f147"}@media screen and (max-width:782px){textarea{-webkit-appearance:none}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:3px 10px;min-height:40px}::-webkit-datetime-edit{line-height:1.875}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox],input[type=checkbox]{-webkit-appearance:none}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox]{margin-bottom:8px}.widefat tfoot td input[type=checkbox]:before,.widefat th input[type=checkbox]:before,.widefat thead td input[type=checkbox]:before,input[type=checkbox]:checked:before{width:1.875rem;height:1.875rem;margin:-.1875rem -.3125rem}input[type=checkbox],input[type=radio]{height:1.5625rem;width:1.5625rem}.wp-admin p input[type=checkbox],.wp-admin p input[type=radio]{margin-top:-.1875rem}input[type=radio]:checked:before{vertical-align:middle;width:.5625rem;height:.5625rem;margin:.4375rem;line-height:.76190476}.wp-upload-form input[type=submit]{margin-top:10px}.wp-admin .form-table select,.wp-core-ui select{min-height:40px;font-size:16px;line-height:1.625;padding:5px 24px 5px 8px}.wp-admin .button-cancel{margin-bottom:0;padding:2px 0;font-size:14px;vertical-align:middle}#adduser .form-field input,#createuser .form-field input{width:100%}.form-table{box-sizing:border-box}.form-table td,.form-table th,.label-responsive{display:block;width:auto;vertical-align:middle}.label-responsive{margin:.5em 0}.export-filters li{margin-bottom:0}.form-table .color-palette td{display:table-cell;width:15px}.form-table table.color-palette{margin-right:10px}input,textarea{font-size:16px}#profile-page .form-table textarea,.form-table span.description,.form-table td input[type=email],.form-table td input[type=password],.form-table td input[type=text],.form-table td select,.form-table td textarea{width:100%;display:block;max-width:none;box-sizing:border-box}.form-table .form-required.form-invalid td:after{float:right;margin:-30px 3px 0 0}.form-table input[type=text].small-text,input[type=number].small-text,input[type=password].small-text,input[type=search].small-text,input[type=text].small-text{width:auto;max-width:4.375em;display:inline;padding:3px 6px;margin:0 3px}#pass-strength-result{width:100%;box-sizing:border-box;padding:8px}p.search-box{float:none;position:absolute;bottom:0;width:98%;height:90px;margin-bottom:20px}p.search-box input[name="s"]{float:none;width:100%;margin-bottom:10px;vertical-align:middle}p.search-box input[type=submit]{margin-bottom:10px}.form-table span.description{display:inline;padding:4px 0 0;line-height:1.4;font-size:14px}.form-table th{padding:10px 0 0 0;border-bottom:0}.form-table td{margin-bottom:0;padding:4px 0 6px 0}.form-table.permalink-structure td code{margin-left:32px;display:inline-block}.form-table.permalink-structure td input[type=text]{margin-left:32px;margin-top:4px;width:96%}.form-table input.regular-text{width:100%}.form-table label{font-size:14px}.background-position-control .button-group>label{font-size:0}.form-table fieldset label{display:block}#local-time,#utc-time{display:block;float:none;margin-top:.5em}.form-field #domain{max-width:none}.wp-pwd{position:relative}#profile-page .form-table #pass1{padding-right:90px}.wp-pwd button.button{background:0 0;border:1px solid transparent;box-shadow:none;line-height:2;margin:0;padding:5px 9px;position:absolute;right:0;top:0;width:2.375rem;height:2.375rem;min-width:40px;min-height:40px}.wp-pwd button.wp-hide-pw{right:2.5rem}.wp-pwd button.button:focus,.wp-pwd button.button:hover{background:0 0}.wp-pwd button.button:active{background:0 0;box-shadow:none;transform:none}.wp-pwd .button .text{display:none}.wp-pwd [type=password],.wp-pwd [type=text]{line-height:2;padding-right:5rem}.wp-cancel-pw .dashicons-no{display:inline-block}.options-general-php input[type=text].small-text{max-width:6.25em;margin:0}.tools-privacy-policy-page form.wp-create-privacy-page{margin-bottom:1em}.tools-privacy-policy-page input#set-page,.tools-privacy-policy-page select{margin:10px 0 0}.tools-privacy-policy-page .wp-create-privacy-page span{display:block;margin-bottom:1em}.tools-privacy-policy-page .wp-create-privacy-page .button{margin-left:0}.wp-list-table.privacy_requests tr:not(.inline-edit-row):not(.no-items) td.column-primary:not(.check-column){display:table-cell}.wp-list-table.privacy_requests.widefat th input,.wp-list-table.privacy_requests.widefat thead td input{margin-left:5px}.wp-privacy-request-form-field input[type=text]{width:100%;margin-bottom:10px;vertical-align:middle}.regular-text{max-width:100%}}@media only screen and (max-width:768px){.form-field input[type=email],.form-field input[type=password],.form-field input[type=text],.form-field select,.form-field textarea{width:99%}.form-wrap .form-field{padding:0}}@media only screen and (max-height:480px),screen and (max-width:450px){.file-editor-warning .notification-dialog,.request-filesystem-credentials-dialog .notification-dialog{width:100%;height:100%;max-height:100%;position:fixed;top:0;margin:0;left:0}}@media screen and (max-width:600px){.color-option{width:49%}}@media only screen and (max-width:320px){.options-general-php .date-time-text.date-time-custom-text{min-width:0;margin-right:.5em}}@keyframes rotation{0%{transform:rotate(0)}100%{transform:rotate(359deg)}} \ No newline at end of file +button,input,select,textarea{box-sizing:border-box;font-family:inherit;font-size:inherit;font-weight:inherit}input,textarea{font-size:14px}textarea{overflow:auto;padding:2px 6px;line-height:1.42857143;resize:vertical}label{cursor:pointer}input,select{margin:0 1px}textarea.code{padding:4px 6px 1px 6px}input[type=color],input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week],select,textarea{box-shadow:0 0 0 transparent;border-radius:4px;border:1px solid #7e8993;background-color:#fff;color:#32373c}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{padding:0 8px;line-height:2;min-height:30px}::-webkit-datetime-edit{line-height:1.85714286}input[type=checkbox]:focus,input[type=color]:focus,input[type=date]:focus,input[type=datetime-local]:focus,input[type=datetime]:focus,input[type=email]:focus,input[type=month]:focus,input[type=number]:focus,input[type=password]:focus,input[type=radio]:focus,input[type=search]:focus,input[type=tel]:focus,input[type=text]:focus,input[type=time]:focus,input[type=url]:focus,input[type=week]:focus,select:focus,textarea:focus{border-color:#007cba;box-shadow:0 0 0 1px #007cba;outline:2px solid transparent}input[type=email],input[type=url]{direction:ltr}input[type=checkbox],input[type=radio]{border:1px solid #7e8993;border-radius:4px;background:#fff;color:#555;clear:none;cursor:pointer;display:inline-block;line-height:0;height:1rem;margin:-.25rem .25rem 0 0;outline:0;padding:0!important;text-align:center;vertical-align:middle;width:1rem;min-width:1rem;-webkit-appearance:none;box-shadow:inset 0 1px 2px rgba(0,0,0,.1);transition:.05s border-color ease-in-out}input[type=radio]:checked+label:before{color:#82878c}.wp-core-ui input[type=reset]:active,.wp-core-ui input[type=reset]:hover{color:#006799}.wp-admin p input[type=checkbox],.wp-admin p input[type=radio],td>input[type=checkbox]{margin-top:0}.wp-admin p label input[type=checkbox]{margin-top:-4px}.wp-admin p label input[type=radio]{margin-top:-2px}input[type=radio]{border-radius:50%;margin-right:.25rem;line-height:.71428571}input[type=checkbox]:checked::before,input[type=radio]:checked::before{float:left;display:inline-block;vertical-align:middle;width:1rem;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}input[type=checkbox]:checked::before{content:url(data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%27http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%27%20viewBox%3D%270%200%2020%2020%27%3E%3Cpath%20d%3D%27M14.83%204.89l1.34.94-5.81%208.38H9.02L5.78%209.67l1.34-1.25%202.57%202.4z%27%20fill%3D%27%231e8cbe%27%2F%3E%3C%2Fsvg%3E);margin:-.1875rem 0 0 -.25rem;height:1.3125rem;width:1.3125rem}input[type=radio]:checked::before{content:"";border-radius:50%;width:.5rem;height:.5rem;margin:.1875rem;background-color:#1e8cbe;line-height:1.14285714}@-moz-document url-prefix(){.form-table input.tog,input[type=checkbox],input[type=radio]{margin-bottom:-1px}}input[type=search]{-webkit-appearance:textfield}input[type=search]::-webkit-search-decoration{display:none}.wp-admin input[type=file]{padding:3px 0;cursor:pointer}input.readonly,input[readonly],textarea.readonly,textarea[readonly]{background-color:#eee}::-webkit-input-placeholder{color:#72777c}::-moz-placeholder{color:#72777c;opacity:1}:-ms-input-placeholder{color:#72777c}.form-invalid input,.form-invalid input:focus,.form-invalid select,.form-invalid select:focus{border-color:#dc3232!important;box-shadow:0 0 2px rgba(204,0,0,.8)}.form-table .form-required.form-invalid td:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin-left:-25px;vertical-align:middle}.form-table .form-required.user-pass1-wrap.form-invalid td:after{content:""}.form-table .form-required.user-pass1-wrap.form-invalid .password-input-wrapper:after{content:"\f534";font:normal 20px/1 dashicons;color:#dc3232;margin:0 6px 0 -29px;vertical-align:middle}.form-input-tip{color:#666}input.disabled,input:disabled,select.disabled,select:disabled,textarea.disabled,textarea:disabled{background:rgba(255,255,255,.5);border-color:rgba(222,222,222,.75);box-shadow:inset 0 1px 2px rgba(0,0,0,.04);color:rgba(51,51,51,.5)}input[type=file].disabled,input[type=file]:disabled,input[type=range].disabled,input[type=range]:disabled{background:0 0;box-shadow:none;cursor:default}input[type=checkbox].disabled,input[type=checkbox].disabled:checked:before,input[type=checkbox]:disabled,input[type=checkbox]:disabled:checked:before,input[type=radio].disabled,input[type=radio].disabled:checked:before,input[type=radio]:disabled,input[type=radio]:disabled:checked:before{opacity:.7}.wp-core-ui select{font-size:14px;line-height:2;color:#32373c;border-color:#7e8993;box-shadow:none;border-radius:3px;padding:0 24px 0 8px;min-height:30px;max-width:25rem;-webkit-appearance:none;background:#fff url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23555%22%2F%3E%3C%2Fsvg%3E') no-repeat right 5px top 55%;background-size:16px 16px;cursor:pointer;vertical-align:middle}.wp-core-ui select:hover{color:#007cba}.wp-core-ui select:focus{border-color:#007cba;color:#016087;box-shadow:0 0 0 1px #007cba}.wp-core-ui select:active{border-color:#999;box-shadow:none}.wp-core-ui select.disabled,.wp-core-ui select:disabled{color:#a0a5aa;border-color:#ddd;background-color:#f7f7f7;background-image:url('data:image/svg+xml;charset=US-ASCII,%3Csvg%20width%3D%2220%22%20height%3D%2220%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%3E%3Cpath%20d%3D%22M5%206l5%205%205-5%202%201-7%207-7-7%202-1z%22%20fill%3D%22%23a0a5aa%22%2F%3E%3C%2Fsvg%3E');box-shadow:none;text-shadow:0 1px 0 #fff;cursor:default;transform:none}.wp-core-ui select:-moz-focusring{color:transparent;text-shadow:0 0 0 #016087}.wp-core-ui select::-ms-value{background:0 0;color:#555}.wp-core-ui select:hover::-ms-value{color:#007cba}.wp-core-ui select:focus::-ms-value{color:#016087}.wp-core-ui select.disabled::-ms-value,.wp-core-ui select:disabled::-ms-value{color:#a0a5aa}.wp-core-ui select::-ms-expand{display:none}.wp-admin .button-cancel{display:inline-block;min-height:28px;padding:0 5px;line-height:2}.meta-box-sortables select{max-width:100%}.meta-box-sortables input{vertical-align:middle}.misc-pub-post-status select{margin-top:0}.wp-core-ui select[multiple]{height:auto;padding-right:8px;background:#fff}.submit{padding:1.5em 0;margin:5px 0;border-bottom-left-radius:3px;border-bottom-right-radius:3px;border:none}form p.submit a.cancel:hover{text-decoration:none}p.submit{text-align:left;max-width:100%;margin-top:20px;padding-top:10px}.textright p.submit{border:none;text-align:right}table.form-table+input+input+p.submit,table.form-table+input+p.submit,table.form-table+p.submit{border-top:none;padding-top:0}#major-publishing-actions input,#minor-publishing-actions .preview,#minor-publishing-actions input{text-align:center}input.all-options,textarea.all-options{width:250px}input.large-text,textarea.large-text{width:99%}.regular-text{width:25em}input.small-text{width:50px;padding:0 6px}label input.small-text{margin-top:-4px}input[type=number].small-text{width:65px;padding-right:0}input.tiny-text{width:35px}input[type=number].tiny-text{width:45px;padding-right:0}#doaction,#doaction2,#post-query-submit{margin:0 8px 0 0}.tablenav .actions select{float:left;margin-right:6px;max-width:12.5rem}#timezone_string option{margin-left:1em}.wp-cancel-pw>.dashicons,.wp-hide-pw>.dashicons{position:relative;top:3px;width:1.25rem;height:1.25rem;top:.25rem;font-size:20px}.wp-cancel-pw .dashicons-no{display:none}#your-profile label+a,label{vertical-align:middle}#your-profile label+a,fieldset label{vertical-align:middle}.options-media-php [for*="_size_"]{min-width:10em;vertical-align:baseline}.options-media-php .small-text[name*="_size_"]{margin:0 0 1em}#misc-publishing-actions label{vertical-align:baseline}#pass-strength-result{background-color:#eee;border:1px solid #ddd;color:#23282d;margin:-1px 1px 5px;padding:3px 5px;text-align:center;width:25em;box-sizing:border-box;opacity:0}#pass-strength-result.short{background-color:#f1adad;border-color:#e35b5b;opacity:1}#pass-strength-result.bad{background-color:#fbc5a9;border-color:#f78b53;opacity:1}#pass-strength-result.good{background-color:#ffe399;border-color:#ffc733;opacity:1}#pass-strength-result.strong{background-color:#c1e1b9;border-color:#83c373;opacity:1}#pass1-text.short,#pass1.short{border-color:#e35b5b}#pass1-text.bad,#pass1.bad{border-color:#f78b53}#pass1-text.good,#pass1.good{border-color:#ffc733}#pass1-text.strong,#pass1.strong{border-color:#83c373}.pw-weak{display:none}.indicator-hint{padding-top:8px}.wp-pwd [type=password],.wp-pwd [type=text]{margin-bottom:0;min-height:30px}.wp-pwd input::-ms-reveal{display:none}#pass1-text,.show-password #pass1{display:none}#pass1-text::-ms-clear{display:none}.show-password #pass1-text{display:inline-block}p.search-box{float:right;margin:0}.network-admin.themes-php p.search-box{clear:left}.search-box input[name="s"],.tablenav .search-plugins input[name="s"],.tagsdiv .newtag{float:left;margin:0 4px 0 0}.js.plugins-php .search-box .wp-filter-search{margin:0;width:280px}input[type=email].ui-autocomplete-loading,input[type=text].ui-autocomplete-loading{background-image:url(../images/loading.gif);background-repeat:no-repeat;background-position:right center;visibility:visible}input.ui-autocomplete-input.open{border-bottom-color:transparent}ul#add-to-blog-users{margin:0 0 0 14px}.ui-autocomplete{padding:0;margin:0;list-style:none;position:absolute;z-index:10000;border:1px solid #5b9dd9;box-shadow:0 1px 2px rgba(30,140,190,.8);background-color:#fff}.ui-autocomplete li{margin-bottom:0;padding:4px 10px;white-space:nowrap;text-align:left;cursor:pointer}.ui-autocomplete .ui-state-focus{background-color:#ddd}.wp-tags-autocomplete .ui-state-focus{background-color:#0073aa;color:#fff}.form-table{border-collapse:collapse;margin-top:.5em;width:100%;clear:both}.form-table,.form-table td,.form-table td p,.form-table th{font-size:14px}.form-table td{margin-bottom:9px;padding:15px 10px;line-height:1.3;vertical-align:middle}.form-table th,.form-wrap label{color:#23282d;font-weight:400;text-shadow:none;vertical-align:baseline}.form-table th{vertical-align:top;text-align:left;padding:20px 10px 20px 0;width:200px;line-height:1.3;font-weight:600}.form-table .td-full,.form-table th.th-full{width:auto;padding:20px 10px 20px 0;font-weight:400}.form-table td p{margin-top:4px;margin-bottom:0}.form-table .date-time-doc{margin-top:1em}.form-table p.timezone-info{margin:1em 0}.form-table td fieldset label{margin:.35em 0 .5em!important;display:inline-block}.form-table td fieldset p label{margin-top:0!important}.form-table td fieldset label,.form-table td fieldset li,.form-table td fieldset p{line-height:1.4}.form-table input.tog,.form-table input[type=radio]{margin-top:-4px;margin-right:4px;float:none}.form-table .pre{padding:8px;margin:0}table.form-table td .updated{font-size:13px}table.form-table td .updated p{font-size:13px;margin:.3em 0}#profile-page .form-table textarea{width:500px;margin-bottom:6px}#profile-page .form-table #rich_editing{margin-right:5px}#your-profile legend{font-size:22px}#display_name{width:15em}#adduser .form-field input,#createuser .form-field input{width:25em}.color-option{display:inline-block;width:24%;padding:5px 15px 15px;box-sizing:border-box;margin-bottom:3px}.color-option.selected,.color-option:hover{background:#ddd}.color-palette{width:100%;border-spacing:0;border-collapse:collapse}.color-palette td{height:20px;padding:0;border:none}.color-option{cursor:pointer}.new-application-password-notice.notice{margin-top:20px;margin-bottom:0}.tool-box .title{margin:8px 0;font-size:18px;font-weight:400;line-height:24px}.label-responsive{vertical-align:middle}#export-filters p{margin:0 0 1em}#export-filters p.submit{margin:7px 0 5px}.card{position:relative;margin-top:20px;padding:.7em 2em 1em;min-width:255px;max-width:520px;border:1px solid #ccd0d4;box-shadow:0 1px 1px rgba(0,0,0,.04);background:#fff;box-sizing:border-box}.pressthis h4{margin:2em 0 1em}.pressthis textarea{width:100%;font-size:1em}#pressthis-code-wrap{overflow:auto}.pressthis-bookmarklet-wrapper{margin:20px 0 8px;vertical-align:top;position:relative;z-index:1}.pressthis-bookmarklet,.pressthis-bookmarklet:active,.pressthis-bookmarklet:focus,.pressthis-bookmarklet:hover{display:inline-block;position:relative;cursor:move;color:#32373c;background:#e5e5e5;border-radius:5px;border:1px solid #b4b9be;font-style:normal;line-height:16px;font-size:14px;text-decoration:none}.pressthis-bookmarklet:active{outline:0}.pressthis-bookmarklet:after{content:"";width:70%;height:55%;z-index:-1;position:absolute;right:10px;bottom:9px;background:0 0;transform:skew(20deg) rotate(6deg);box-shadow:0 10px 8px rgba(0,0,0,.6)}.pressthis-bookmarklet:hover:after{transform:skew(20deg) rotate(9deg);box-shadow:0 10px 8px rgba(0,0,0,.7)}.pressthis-bookmarklet span{display:inline-block;margin:0 0 0;padding:0 12px 8px 9px}.pressthis-bookmarklet span:before{color:#72777c;font:normal 20px/1 dashicons;content:"\f157";position:relative;display:inline-block;top:4px;margin-right:4px}.pressthis-js-toggle{margin-left:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle.button.button{margin-left:10px;padding:0;height:auto;vertical-align:top}.pressthis-js-toggle .dashicons{margin:5px 8px 6px 7px;color:#555d66}.timezone-info code{white-space:nowrap}.defaultavatarpicker .avatar{margin:2px 0;vertical-align:middle}.options-general-php .date-time-text{display:inline-block;min-width:10em}.options-general-php input.small-text{width:56px;margin:-2px 0}.options-general-php .spinner{float:none;margin:-3px 3px 0}.options-general-php .language-install-spinner,.settings-php .language-install-spinner{display:inline-block;float:none;margin:-3px 5px 0;vertical-align:middle}.form-table.permalink-structure .available-structure-tags li{float:left;margin-right:5px}.setup-php textarea{max-width:100%}.form-field #site-address{max-width:25em}.form-field #domain{max-width:22em}.form-field #admin-email,.form-field #blog_last_updated,.form-field #blog_registered,.form-field #path,.form-field #site-title{max-width:25em}.form-field #path{margin-bottom:5px}#search-sites,#search-users{max-width:60%}.request-filesystem-credentials-dialog{display:none;visibility:visible}.request-filesystem-credentials-dialog .notification-dialog{top:10%;max-height:85%}.request-filesystem-credentials-dialog-content{margin:25px}#request-filesystem-credentials-title{font-size:1.3em;margin:1em 0}.request-filesystem-credentials-form legend{font-size:1em;padding:1.33em 0;font-weight:600}.request-filesystem-credentials-form input[type=password],.request-filesystem-credentials-form input[type=text]{display:block}.request-filesystem-credentials-dialog input[type=password],.request-filesystem-credentials-dialog input[type=text]{width:100%}.request-filesystem-credentials-form .field-title{font-weight:600}.request-filesystem-credentials-dialog label[for=hostname],.request-filesystem-credentials-dialog label[for=private_key],.request-filesystem-credentials-dialog label[for=public_key]{display:block;margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:left;width:48%}.request-filesystem-credentials-dialog .ftp-password{margin-left:4%}.request-filesystem-credentials-dialog .request-filesystem-credentials-action-buttons{text-align:right}.request-filesystem-credentials-dialog label[for=ftp]{margin-right:10px}.request-filesystem-credentials-dialog #auth-keys-desc{margin-bottom:0}#request-filesystem-credentials-dialog .button:not(:last-child){margin-right:10px}#request-filesystem-credentials-form .cancel-button{display:none}#request-filesystem-credentials-dialog .cancel-button{display:inline}.request-filesystem-credentials-dialog .ftp-password,.request-filesystem-credentials-dialog .ftp-username{float:none;width:auto}.request-filesystem-credentials-dialog .ftp-username{margin-bottom:1em}.request-filesystem-credentials-dialog .ftp-password{margin:0}.request-filesystem-credentials-dialog .ftp-password em{color:#888}.request-filesystem-credentials-dialog label{display:block;line-height:1.5;margin-bottom:1em}.request-filesystem-credentials-form legend{padding-bottom:0}.request-filesystem-credentials-form #ssh-keys legend{font-size:1.3em}.request-filesystem-credentials-form .notice{margin:0 0 20px 0;clear:both}.tools-privacy-policy-page form{margin-bottom:1.3em}.tools-privacy-policy-page input.button,.tools-privacy-policy-page select{margin:0 1px 0 6px}.tools-privacy-edit{margin:1.5em 0}.tools-privacy-policy-page span{line-height:2}.privacy_requests .column-email{width:40%}.privacy_requests .column-type{text-align:center}.privacy_requests tfoot td:first-child,.privacy_requests thead td:first-child{border-left:4px solid #fff}.privacy_requests tbody th{border-left:4px solid #fff;background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests .row-actions{color:#72777c}.privacy_requests .row-actions.processing{position:static}.privacy_requests tbody .has-request-results th{box-shadow:none}.privacy_requests tbody .request-results th .notice{margin:0 0 5px}.privacy_requests tbody td{background:#fff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.1)}.privacy_requests tbody .has-request-results td{box-shadow:none}.privacy_requests .next_steps .button{word-wrap:break-word;white-space:normal}.privacy_requests .status-request-confirmed td,.privacy_requests .status-request-confirmed th{background-color:#f7fcfe;border-left-color:#00a0d2}.privacy_requests .status-request-failed td,.privacy_requests .status-request-failed th{background-color:#fef7f1;border-left-color:#d64d21}.privacy_requests .export_personal_data_failed a{vertical-align:baseline}.status-label{font-weight:600}.status-label.status-request-pending{font-weight:400;font-style:italic;color:#6c7781}.status-label.status-request-failed{color:#a00;font-weight:600}.wp-privacy-request-form{clear:both}.wp-privacy-request-form-field{margin:1.5em 0}.wp-privacy-request-form label{font-weight:600;line-height:1.5;padding-bottom:.5em;display:block}.wp-privacy-request-form input{margin:0}.email-personal-data::before{display:inline-block;font:normal 20px/1 dashicons;margin:3px 5px 0 -2px;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;vertical-align:top}.email-personal-data--sending::before{color:#f56e28;content:"\f463";animation:rotation 2s infinite linear}.email-personal-data--sent::before{color:#79ba49;content:"\f147"}@media screen and (max-width:782px){textarea{-webkit-appearance:none}input[type=date],input[type=datetime-local],input[type=datetime],input[type=email],input[type=month],input[type=number],input[type=password],input[type=search],input[type=tel],input[type=text],input[type=time],input[type=url],input[type=week]{-webkit-appearance:none;padding:3px 10px;min-height:40px}::-webkit-datetime-edit{line-height:1.875}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox],input[type=checkbox]{-webkit-appearance:none}.widefat tfoot td input[type=checkbox],.widefat th input[type=checkbox],.widefat thead td input[type=checkbox]{margin-bottom:8px}.widefat tfoot td input[type=checkbox]:before,.widefat th input[type=checkbox]:before,.widefat thead td input[type=checkbox]:before,input[type=checkbox]:checked:before{width:1.875rem;height:1.875rem;margin:-.1875rem -.3125rem}input[type=checkbox],input[type=radio]{height:1.5625rem;width:1.5625rem}.wp-admin p input[type=checkbox],.wp-admin p input[type=radio]{margin-top:-.1875rem}input[type=radio]:checked:before{vertical-align:middle;width:.5625rem;height:.5625rem;margin:.4375rem;line-height:.76190476}.wp-upload-form input[type=submit]{margin-top:10px}.wp-admin .form-table select,.wp-core-ui select{min-height:40px;font-size:16px;line-height:1.625;padding:5px 24px 5px 8px}.wp-admin .button-cancel{margin-bottom:0;padding:2px 0;font-size:14px;vertical-align:middle}#adduser .form-field input,#createuser .form-field input{width:100%}.form-table{box-sizing:border-box}.form-table td,.form-table th,.label-responsive{display:block;width:auto;vertical-align:middle}.label-responsive{margin:.5em 0}.export-filters li{margin-bottom:0}.form-table .color-palette td{display:table-cell;width:15px}.form-table table.color-palette{margin-right:10px}input,textarea{font-size:16px}#profile-page .form-table textarea,.form-table span.description,.form-table td input[type=email],.form-table td input[type=password],.form-table td input[type=text],.form-table td select,.form-table td textarea{width:100%;display:block;max-width:none;box-sizing:border-box}.form-table .form-required.form-invalid td:after{float:right;margin:-30px 3px 0 0}.form-table input[type=text].small-text,input[type=number].small-text,input[type=password].small-text,input[type=search].small-text,input[type=text].small-text{width:auto;max-width:4.375em;display:inline;padding:3px 6px;margin:0 3px}#pass-strength-result{width:100%;box-sizing:border-box;padding:8px}p.search-box{float:none;position:absolute;bottom:0;width:98%;height:90px;margin-bottom:20px}p.search-box input[name="s"]{float:none;width:100%;margin-bottom:10px;vertical-align:middle}p.search-box input[type=submit]{margin-bottom:10px}.form-table span.description{display:inline;padding:4px 0 0;line-height:1.4;font-size:14px}.form-table th{padding:10px 0 0 0;border-bottom:0}.form-table td{margin-bottom:0;padding:4px 0 6px 0}.form-table.permalink-structure td code{margin-left:32px;display:inline-block}.form-table.permalink-structure td input[type=text]{margin-left:32px;margin-top:4px;width:96%}.form-table input.regular-text{width:100%}.form-table label{font-size:14px}.background-position-control .button-group>label{font-size:0}.form-table fieldset label{display:block}#local-time,#utc-time{display:block;float:none;margin-top:.5em}.form-field #domain{max-width:none}.wp-pwd{position:relative}#profile-page .form-table #pass1{padding-right:90px}.wp-pwd button.button{background:0 0;border:1px solid transparent;box-shadow:none;line-height:2;margin:0;padding:5px 9px;position:absolute;right:0;top:0;width:2.375rem;height:2.375rem;min-width:40px;min-height:40px}.wp-pwd button.wp-hide-pw{right:2.5rem}.wp-pwd button.button:focus,.wp-pwd button.button:hover{background:0 0}.wp-pwd button.button:active{background:0 0;box-shadow:none;transform:none}.wp-pwd .button .text{display:none}.wp-pwd [type=password],.wp-pwd [type=text]{line-height:2;padding-right:5rem}.wp-cancel-pw .dashicons-no{display:inline-block}.options-general-php input[type=text].small-text{max-width:6.25em;margin:0}.tools-privacy-policy-page form.wp-create-privacy-page{margin-bottom:1em}.tools-privacy-policy-page input#set-page,.tools-privacy-policy-page select{margin:10px 0 0}.tools-privacy-policy-page .wp-create-privacy-page span{display:block;margin-bottom:1em}.tools-privacy-policy-page .wp-create-privacy-page .button{margin-left:0}.wp-list-table.privacy_requests tr:not(.inline-edit-row):not(.no-items) td.column-primary:not(.check-column){display:table-cell}.wp-list-table.privacy_requests.widefat th input,.wp-list-table.privacy_requests.widefat thead td input{margin-left:5px}.wp-privacy-request-form-field input[type=text]{width:100%;margin-bottom:10px;vertical-align:middle}.regular-text{max-width:100%}}@media only screen and (max-width:768px){.form-field input[type=email],.form-field input[type=password],.form-field input[type=text],.form-field select,.form-field textarea{width:99%}.form-wrap .form-field{padding:0}}@media only screen and (max-height:480px),screen and (max-width:450px){.file-editor-warning .notification-dialog,.request-filesystem-credentials-dialog .notification-dialog{width:100%;height:100%;max-height:100%;position:fixed;top:0;margin:0;left:0}}@media screen and (max-width:600px){.color-option{width:49%}}@media only screen and (max-width:320px){.options-general-php .date-time-text.date-time-custom-text{min-width:0;margin-right:.5em}}@keyframes rotation{0%{transform:rotate(0)}100%{transform:rotate(359deg)}} \ No newline at end of file diff --git a/wp-admin/includes/class-wp-application-passwords-list-table.php b/wp-admin/includes/class-wp-application-passwords-list-table.php new file mode 100644 index 0000000000..fc21c59931 --- /dev/null +++ b/wp-admin/includes/class-wp-application-passwords-list-table.php @@ -0,0 +1,248 @@ + __( 'Name' ), + 'created' => __( 'Created' ), + 'last_used' => __( 'Last Used' ), + 'last_ip' => __( 'Last IP' ), + 'revoke' => __( 'Revoke' ), + ); + } + + /** + * Prepares the list of items for displaying. + * + * @since 5.6.0 + */ + public function prepare_items() { + global $user_id; + $this->items = array_reverse( WP_Application_Passwords::get_user_application_passwords( $user_id ) ); + } + + /** + * Handles the name column output. + * + * @since 5.6.0 + * + * @param array $item The current application password item. + */ + public function column_name( $item ) { + echo esc_html( $item['name'] ); + } + + /** + * Handles the created column output. + * + * @since 5.6.0 + * + * @param array $item The current application password item. + */ + public function column_created( $item ) { + if ( empty( $item['created'] ) ) { + echo '—'; + } else { + echo gmdate( get_option( 'date_format', 'r' ), $item['created'] ); + } + } + + /** + * Handles the last used column output. + * + * @since 5.6.0 + * + * @param array $item The current application password item. + */ + public function column_last_used( $item ) { + if ( empty( $item['last_used'] ) ) { + echo '—'; + } else { + echo gmdate( get_option( 'date_format', 'r' ), $item['last_used'] ); + } + } + + /** + * Handles the last ip column output. + * + * @since 5.6.0 + * + * @param array $item The current application password item. + */ + public function column_last_ip( $item ) { + if ( empty( $item['last_ip'] ) ) { + echo '—'; + } else { + echo $item['last_ip']; + } + } + + /** + * Handles the revoke column output. + * + * @since 5.6.0 + */ + public function column_revoke() { + submit_button( __( 'Revoke' ), 'delete', 'revoke-application-password', false ); + } + + /** + * Generates content for a single row of the table + * + * @since 5.6.0 + * + * @param array $item The current item. + * @param string $column_name The current column name. + */ + protected function column_default( $item, $column_name ) { + /** + * Fires for each custom column in the Application Passwords list table. + * + * Custom columns are registered using the {@see 'manage_application-passwords-user_columns'} filter. + * + * @since 5.6.0 + * + * @param string $column_name Name of the custom column. + * @param array $item The application password item. + */ + do_action( "manage_{$this->screen->id}_custom_column", $column_name, $item ); + } + + /** + * Generates custom table navigation to prevent conflicting nonces. + * + * @since 5.6.0 + * + * @param string $which The location of the bulk actions: 'top' or 'bottom'. + */ + protected function display_tablenav( $which ) { + ?> +
+ +
+ +
+ +
+ bulk_actions( $which ); ?> +
+ extra_tablenav( $which ); + $this->pagination( $which ); + ?> +
+
+ '; + $this->single_row_columns( $item ); + echo ''; + } + + /** + * Gets the name of the default primary column. + * + * @since 5.6.0 + * + * @return string Name of the default primary column, in this case, 'name'. + */ + protected function get_default_primary_column_name() { + return 'name'; + } + + /** + * Prints the JavaScript template for the new row item. + * + * @since 5.6.0 + */ + public function print_js_template_row() { + list( $columns, $hidden, , $primary ) = $this->get_column_info(); + + echo ''; + + foreach ( $columns as $column_name => $display_name ) { + $is_primary = $primary === $column_name; + $classes = "{$column_name} column-{$column_name}"; + + if ( $is_primary ) { + $classes .= ' has-row-actions column-primary'; + } + + if ( in_array( $column_name, $hidden, true ) ) { + $classes .= ' hidden'; + } + + printf( '', esc_attr( $classes ), esc_attr( wp_strip_all_tags( $display_name ) ) ); + + switch ( $column_name ) { + case 'name': + echo '{{ data.name }}'; + break; + case 'created': + echo "<# print( wp.date.dateI18n( '" . esc_js( get_option( 'date_format' ) ) . "', data.created ) ) #>"; + break; + case 'last_used': + echo "<# print( data.last_used !== null ? wp.date.dateI18n( '" . esc_js( get_option( 'date_format' ) ) . "', data.last_used ) : '—' ) #>"; + break; + case 'last_ip': + echo "{{ data.last_ip || '—' }}"; + break; + case 'revoke': + echo $this->column_revoke(); + break; + default: + /** + * Fires in the JavaScript row template for each custom column in the Application Passwords list table. + * + * Custom columns are registered using the {@see 'manage_application-passwords-user_columns'} filter. + * + * @since 5.6.0 + * + * @param string $column_name Name of the custom column. + */ + do_action( "manage_{$this->screen->id}_custom_column_js_template", $column_name ); + break; + } + + if ( $is_primary ) { + echo ''; + } + + echo ''; + } + + echo ''; + } +} diff --git a/wp-admin/includes/list-table.php b/wp-admin/includes/list-table.php index 342f48dc9f..b702a5089f 100644 --- a/wp-admin/includes/list-table.php +++ b/wp-admin/includes/list-table.php @@ -33,6 +33,7 @@ function _get_list_table( $class, $args = array() ) { 'WP_Themes_List_Table' => 'themes', 'WP_Theme_Install_List_Table' => array( 'themes', 'theme-install' ), 'WP_Plugins_List_Table' => 'plugins', + 'WP_Application_Passwords_List_Table' => 'application-passwords', // Network Admin. 'WP_MS_Sites_List_Table' => 'ms-sites', diff --git a/wp-admin/includes/user.php b/wp-admin/includes/user.php index 78deb2552e..de7cb989c1 100644 --- a/wp-admin/includes/user.php +++ b/wp-admin/includes/user.php @@ -594,3 +594,61 @@ Please click the following link to activate your user account: wp_specialchars_decode( translate_user_role( $role['name'] ) ) ); } + +/** + * Checks if the Authorize Application Password request is valid. + * + * @since 5.6.0 + * + * @param array $request { + * The array of request data. All arguments are optional and may be empty. + * + * @type string $app_name The suggested name of the application. + * @type string $success_url The url the user will be redirected to after approving the application. + * @type string $reject_url The url the user will be redirected to after rejecting the application. + * } + * @param WP_User $user The user authorizing the application. + * @return true|WP_Error True if the request is valid, a WP_Error object contains errors if not. + */ +function wp_is_authorize_application_password_request_valid( $request, $user ) { + $error = new WP_Error(); + + if ( ! empty( $request['success_url'] ) ) { + $scheme = wp_parse_url( $request['success_url'], PHP_URL_SCHEME ); + + if ( 'http' === $scheme ) { + $error->add( + 'invalid_redirect_scheme', + __( 'The success url must be served over a secure connection.' ) + ); + } + } + + if ( ! empty( $request['reject_url'] ) ) { + $scheme = wp_parse_url( $request['reject_url'], PHP_URL_SCHEME ); + + if ( 'http' === $scheme ) { + $error->add( + 'invalid_redirect_scheme', + __( 'The rejection url must be served over a secure connection.' ) + ); + } + } + + /** + * Fires before application password errors are returned. + * + * @since 5.6.0 + * + * @param WP_Error $error The error object. + * @param array $request The array of request data. + * @param WP_User $user The user authorizing the application. + */ + do_action( 'wp_authorize_application_password_request_errors', $error, $request, $user ); + + if ( $error->has_errors() ) { + return $error; + } + + return true; +} diff --git a/wp-admin/js/application-passwords.js b/wp-admin/js/application-passwords.js new file mode 100644 index 0000000000..1ba6e0e665 --- /dev/null +++ b/wp-admin/js/application-passwords.js @@ -0,0 +1,196 @@ +/** + * @output wp-admin/js/application-passwords.js + */ + +( function( $ ) { + var $appPassSection = $( '#application-passwords-section' ), + $newAppPassForm = $appPassSection.find( '.create-application-password' ), + $newAppPassField = $newAppPassForm.find( '.input' ), + $newAppPassButton = $newAppPassForm.find( '.button' ), + $appPassTwrapper = $appPassSection.find( '.application-passwords-list-table-wrapper' ), + $appPassTbody = $appPassSection.find( 'tbody' ), + $appPassTrNoItems = $appPassTbody.find( '.no-items' ), + $removeAllBtn = $( '#revoke-all-application-passwords' ), + tmplNewAppPass = wp.template( 'new-application-password' ), + tmplAppPassRow = wp.template( 'application-password-row' ), + userId = $( '#user_id' ).val(); + + $newAppPassButton.click( function( e ) { + e.preventDefault(); + var name = $newAppPassField.val(); + + if ( 0 === name.length ) { + $newAppPassField.focus(); + return; + } + + clearErrors(); + $newAppPassField.prop( 'disabled', true ); + $newAppPassButton.prop( 'disabled', true ); + + var request = { + name: name + }; + + /** + * Filters the request data used to create a new Application Password. + * + * @since 5.6.0 + * + * @param {Object} request The request data. + * @param {number} userId The id of the user the password is added for. + */ + request = wp.hooks.applyFilters( 'wp_application_passwords_new_password_request', request, userId ); + + wp.apiRequest( { + path: '/wp/v2/users/' + userId + '/application-passwords', + method: 'POST', + data: request + } ).always( function() { + $newAppPassField.prop( 'disabled', false ); + $newAppPassButton.prop( 'disabled', false ); + } ).done( function( response ) { + $newAppPassField.val( '' ); + $newAppPassButton.prop( 'disabled', false ); + + $newAppPassForm.after( tmplNewAppPass( { + name: name, + password: response.password + } ) ); + $( '.new-application-password-notice' ).focus(); + + $appPassTbody.prepend( tmplAppPassRow( response ) ); + + $appPassTwrapper.show(); + $appPassTrNoItems.remove(); + + /** + * Fires after an application password has been successfully created. + * + * @since 5.6.0 + * + * @param {Object} response The response data from the REST API. + * @param {Object} request The request data used to create the password. + */ + wp.hooks.doAction( 'wp_application_passwords_created_password', response, request ); + } ).fail( handleErrorResponse ); + } ); + + $appPassTbody.on( 'click', '.delete', function( e ) { + e.preventDefault(); + + if ( ! window.confirm( wp.i18n.__( 'Are you sure you want to revoke this password? This action cannot be undone.' ) ) ) { + return; + } + + var $submitButton = $( this ), + $tr = $submitButton.closest( 'tr' ), + uuid = $tr.data( 'uuid' ); + + clearErrors(); + $submitButton.prop( 'disabled', true ); + + wp.apiRequest( { + path: '/wp/v2/users/' + userId + '/application-passwords/' + uuid, + method: 'DELETE' + } ).always( function() { + $submitButton.prop( 'disabled', false ); + } ).done( function( response ) { + if ( response.deleted ) { + if ( 0 === $tr.siblings().length ) { + $appPassTwrapper.hide(); + } + $tr.remove(); + + wp.a11y.speak( wp.i18n.__( 'Application password revoked.' ) ); + } + } ).fail( handleErrorResponse ); + } ); + + $removeAllBtn.on( 'click', function( e ) { + e.preventDefault(); + + if ( ! window.confirm( wp.i18n.__( 'Are you sure you want to revoke all passwords? This action cannot be undone.' ) ) ) { + return; + } + + var $submitButton = $( this ); + + clearErrors(); + $submitButton.prop( 'disabled', true ); + + wp.apiRequest( { + path: '/wp/v2/users/' + userId + '/application-passwords', + method: 'DELETE' + } ).always( function() { + $submitButton.prop( 'disabled', false ); + } ).done( function( response ) { + if ( response.deleted ) { + $appPassTbody.children().remove(); + $appPassSection.children( '.new-application-password' ).remove(); + $appPassTwrapper.hide(); + + wp.a11y.speak( wp.i18n.__( 'All application passwords revoked.' ) ); + } + } ).fail( handleErrorResponse ); + } ); + + $( document ).on( 'click', '.new-application-password-notice .notice-dismiss', function( e ) { + e.preventDefault(); + var $el = $( this ).parent(); + $el.fadeTo( 100, 0, function () { + $el.slideUp( 100, function () { + $el.remove(); + } ); + } ); + } ); + + // If there are no items, don't display the table yet. If there are, show it. + if ( 0 === $appPassTbody.children( 'tr' ).not( $appPassTrNoItems ).length ) { + $appPassTwrapper.hide(); + } + + /** + * Handles an error response from the REST API. + * + * @since 5.6.0 + * + * @param {jqXHR} xhr The XHR object from the ajax call. + * @param {string} textStatus The string categorizing the ajax request's status. + * @param {string} errorThrown The HTTP status error text. + */ + function handleErrorResponse( xhr, textStatus, errorThrown ) { + var errorMessage = errorThrown; + + if ( xhr.responseJSON && xhr.responseJSON.message ) { + errorMessage = xhr.responseJSON.message; + } + + addError( errorMessage ); + } + + /** + * Displays an error message in the Application Passwords section. + * + * @since 5.6.0 + * + * @param {string} message The error message to display. + */ + function addError( message ) { + var $notice = $( '
' ) + .attr( 'role', 'alert' ) + .addClass( 'notice notice-error' ) + .append( $( '

' ).text( message ) ); + + $newAppPassForm.after( $notice ); + } + + /** + * Clears error messages from the Application Passwords section. + * + * @since 5.6.0 + */ + function clearErrors() { + $( '.notice', $appPassSection ).remove(); + } +}( jQuery ) ); diff --git a/wp-admin/js/application-passwords.min.js b/wp-admin/js/application-passwords.min.js new file mode 100644 index 0000000000..745266b328 --- /dev/null +++ b/wp-admin/js/application-passwords.min.js @@ -0,0 +1,2 @@ +/*! This file is auto-generated */ +!function(i){var o=i("#application-passwords-section"),n=o.find(".create-application-password"),p=n.find(".input"),s=n.find(".button"),t=o.find(".application-passwords-list-table-wrapper"),r=o.find("tbody"),d=r.find(".no-items"),e=i("#revoke-all-application-passwords"),l=wp.template("new-application-password"),c=wp.template("application-password-row"),w=i("#user_id").val();function u(e,a,o){var p=o;e.responseJSON&&e.responseJSON.message&&(p=e.responseJSON.message),function(e){var a=i("
").attr("role","alert").addClass("notice notice-error").append(i("

").text(e));n.after(a)}(p)}function f(){i(".notice",o).remove()}s.click(function(e){e.preventDefault();var a=p.val();if(0!==a.length){f(),p.prop("disabled",!0),s.prop("disabled",!0);var o={name:a};o=wp.hooks.applyFilters("wp_application_passwords_new_password_request",o,w),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"POST",data:o}).always(function(){p.prop("disabled",!1),s.prop("disabled",!1)}).done(function(e){p.val(""),s.prop("disabled",!1),n.after(l({name:a,password:e.password})),i(".new-application-password-notice").focus(),r.prepend(c(e)),t.show(),d.remove(),wp.hooks.doAction("wp_application_passwords_created_password",e,o)}).fail(u)}else p.focus()}),r.on("click",".delete",function(e){if(e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke this password? This action cannot be undone."))){var a=i(this),o=a.closest("tr"),p=o.data("uuid");f(),a.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords/"+p,method:"DELETE"}).always(function(){a.prop("disabled",!1)}).done(function(e){e.deleted&&(0===o.siblings().length&&t.hide(),o.remove(),wp.a11y.speak(wp.i18n.__("Application password revoked.")))}).fail(u)}}),e.on("click",function(e){if(e.preventDefault(),window.confirm(wp.i18n.__("Are you sure you want to revoke all passwords? This action cannot be undone."))){var a=i(this);f(),a.prop("disabled",!0),wp.apiRequest({path:"/wp/v2/users/"+w+"/application-passwords",method:"DELETE"}).always(function(){a.prop("disabled",!1)}).done(function(e){e.deleted&&(r.children().remove(),o.children(".new-application-password").remove(),t.hide(),wp.a11y.speak(wp.i18n.__("All application passwords revoked.")))}).fail(u)}}),i(document).on("click",".new-application-password-notice .notice-dismiss",function(e){e.preventDefault();var a=i(this).parent();a.fadeTo(100,0,function(){a.slideUp(100,function(){a.remove()})})}),0===r.children("tr").not(d).length&&t.hide()}(jQuery); \ No newline at end of file diff --git a/wp-admin/js/auth-app.js b/wp-admin/js/auth-app.js new file mode 100644 index 0000000000..808bb0754b --- /dev/null +++ b/wp-admin/js/auth-app.js @@ -0,0 +1,152 @@ +/** + * @output wp-admin/js/auth-app.js + */ + +/* global authApp */ + +( function( $, authApp ) { + var $appNameField = $( '#app_name' ), + $approveBtn = $( '#approve' ), + $rejectBtn = $( '#reject' ), + $form = $appNameField.closest( 'form' ), + context = { + userLogin: authApp.user_login, + successUrl: authApp.success, + rejectUrl: authApp.reject + }; + + $approveBtn.click( function( e ) { + var name = $appNameField.val(); + + e.preventDefault(); + + if ( 0 === name.length ) { + $appNameField.focus(); + return; + } + + $appNameField.prop( 'disabled', true ); + $approveBtn.prop( 'disabled', true ); + + var request = { + name: name + }; + + /** + * Filters the request data used to Authorize an Application Password request. + * + * @since 5.6.0 + * + * @param {Object} request The request data. + * @param {Object} context Context about the Application Password request. + * @param {string} context.userLogin The user's login username. + * @param {string} context.successUrl The URL the user will be redirected to after approving the request. + * @param {string} context.rejectUrl The URL the user will be redirected to after rejecting the request. + */ + request = wp.hooks.applyFilters( 'wp_application_passwords_approve_app_request', request, context ); + + wp.apiRequest( { + path: '/wp/v2/users/me/application-passwords', + method: 'POST', + data: request + } ).done( function( response, textStatus, jqXHR ) { + + /** + * Fires when an Authorize Application Password request has been successfully approved. + * + * @since 5.6.0 + * + * @param {Object} response The response from the REST API. + * @param {string} response.password The newly created password. + * @param {string} textStatus The status of the request. + * @param {jqXHR} jqXHR The underlying jqXHR object that made the request. + */ + wp.hooks.doAction( 'wp_application_passwords_approve_app_request_success', response, textStatus, jqXHR ); + + var raw = authApp.success, + url, message, $notice; + + if ( raw ) { + url = raw + ( -1 === raw.indexOf( '?' ) ? '?' : '&' ) + + 'user_login=' + encodeURIComponent( authApp.user_login ) + + '&password=' + encodeURIComponent( response.password ); + + window.location = url; + } else { + message = wp.i18n.sprintf( + wp.i18n.__( 'Your new password for %1$s is: %2$s.' ), + '', + '' + ); + $notice = $( '
' ) + .attr( 'role', 'alert' ) + .attr( 'tabindex', 0 ) + .addClass( 'notice notice-success notice-alt' ) + .append( $( '

' ).html( message ) ); + + // We're using .text() to write the variables to avoid any chance of XSS. + $( 'strong', $notice ).text( name ); + $( 'kbd', $notice ).text( response.password ); + + $form.replaceWith( $notice ); + $notice.focus(); + } + } ).fail( function( jqXHR, textStatus, errorThrown ) { + var errorMessage = errorThrown, + error = null; + + if ( jqXHR.responseJSON ) { + error = jqXHR.responseJSON; + + if ( error.message ) { + errorMessage = error.message; + } + } + + var $notice = $( '
' ) + .attr( 'role', 'alert' ) + .addClass( 'notice notice-error' ) + .append( $( '

' ).text( errorMessage ) ); + + $( 'h1' ).after( $notice ); + + $appNameField.prop( 'disabled', false ); + $approveBtn.prop( 'disabled', false ); + + /** + * Fires when an Authorize Application Password request encountered an error when trying to approve the request. + * + * @since 5.6.0 + * + * @param {Object|null} error The error from the REST API. May be null if the server did not send proper JSON. + * @param {string} textStatus The status of the request. + * @param {string} errorThrown The error message associated with the response status code. + * @param {jqXHR} jqXHR The underlying jqXHR object that made the request. + */ + wp.hooks.doAction( 'wp_application_passwords_approve_app_request_success', error, textStatus, jqXHR ); + } ); + } ); + + $rejectBtn.click( function( e ) { + e.preventDefault(); + + /** + * Fires when an Authorize Application Password request has been rejected by the user. + * + * @since 5.6.0 + * + * @param {Object} context Context about the Application Password request. + * @param {string} context.userLogin The user's login username. + * @param {string} context.successUrl The URL the user will be redirected to after approving the request. + * @param {string} context.rejectUrl The URL the user will be redirected to after rejecting the request. + */ + wp.hooks.doAction( 'wp_application_passwords_reject_app', context ); + + // @todo: Make a better way to do this so it feels like less of a semi-open redirect. + window.location = authApp.reject; + } ); + + $form.on( 'submit', function( e ) { + e.preventDefault(); + } ); +}( jQuery, authApp ) ); diff --git a/wp-admin/js/auth-app.min.js b/wp-admin/js/auth-app.min.js new file mode 100644 index 0000000000..9ad5a24ee6 --- /dev/null +++ b/wp-admin/js/auth-app.min.js @@ -0,0 +1,2 @@ +/*! This file is auto-generated */ +!function(i,c){var r=i("#app_name"),d=i("#approve"),e=i("#reject"),l=r.closest("form"),o={userLogin:c.user_login,successUrl:c.success,rejectUrl:c.reject};d.click(function(e){var n=r.val();if(e.preventDefault(),0!==n.length){r.prop("disabled",!0),d.prop("disabled",!0);var s={name:n};s=wp.hooks.applyFilters("wp_application_passwords_approve_app_request",s,o),wp.apiRequest({path:"/wp/v2/users/me/application-passwords",method:"POST",data:s}).done(function(e,s,o){wp.hooks.doAction("wp_application_passwords_approve_app_request_success",e,s,o);var p,a,t,r=c.success;r?(p=r+(-1===r.indexOf("?")?"?":"&")+"user_login="+encodeURIComponent(c.user_login)+"&password="+encodeURIComponent(e.password),window.location=p):(a=wp.i18n.sprintf(wp.i18n.__("Your new password for %1$s is: %2$s."),"",""),t=i("
").attr("role","alert").attr("tabindex",0).addClass("notice notice-success notice-alt").append(i("

").html(a)),i("strong",t).text(n),i("kbd",t).text(e.password),l.replaceWith(t),t.focus())}).fail(function(e,s,o){var p=o,a=null;e.responseJSON&&(a=e.responseJSON).message&&(p=a.message);var t=i("
").attr("role","alert").addClass("notice notice-error").append(i("

").text(p));i("h1").after(t),r.prop("disabled",!1),d.prop("disabled",!1),wp.hooks.doAction("wp_application_passwords_approve_app_request_success",a,s,e)})}else r.focus()}),e.click(function(e){e.preventDefault(),wp.hooks.doAction("wp_application_passwords_reject_app",o),window.location=c.reject}),l.on("submit",function(e){e.preventDefault()})}(jQuery,authApp); \ No newline at end of file diff --git a/wp-admin/user-edit.php b/wp-admin/user-edit.php index 8c7088e96d..29e8f1a994 100644 --- a/wp-admin/user-edit.php +++ b/wp-admin/user-edit.php @@ -27,6 +27,10 @@ if ( ! $user_id && IS_PROFILE_PAGE ) { wp_enqueue_script( 'user-profile' ); +if ( wp_is_application_passwords_available_for_user( $user_id ) ) { + wp_enqueue_script( 'application-passwords' ); +} + if ( IS_PROFILE_PAGE ) { $title = __( 'Profile' ); } else { @@ -702,6 +706,39 @@ endif; + + +
+

+

+
+ + + + + + +
+ +
+ 'application-passwords-user' ) ); + $application_passwords_list_table->prepare_items(); + $application_passwords_list_table->display(); + ?> +
+
+ + + + + + + + wp_generate_uuid4(), + 'name' => $args['name'], + 'password' => $hashed_password, + 'created' => time(), + 'last_used' => null, + 'last_ip' => null, + ); + + $passwords = static::get_user_application_passwords( $user_id ); + $passwords[] = $new_item; + $saved = static::set_user_application_passwords( $user_id, $passwords ); + + if ( ! $saved ) { + return new WP_Error( 'db_error', __( 'Could not save application password.' ) ); + } + + /** + * Fires when an application password is created. + * + * @since 5.6.0 + * + * @param int $user_id The user ID. + * @param array $new_item The details about the created password. + * @param string $new_password The unhashed generated app password. + * @param array $args Information used to create the application password. + */ + do_action( 'wp_create_application_password', $user_id, $new_item, $new_password, $args ); + + return array( $new_password, $new_item ); + } + + /** + * Gets a user's application passwords. + * + * @since 5.6.0 + * + * @param int $user_id User ID. + * @return array The list of app passwords. + */ + public static function get_user_application_passwords( $user_id ) { + $passwords = get_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, true ); + + if ( ! is_array( $passwords ) ) { + return array(); + } + + $save = false; + + foreach ( $passwords as $i => $password ) { + if ( ! isset( $password['uuid'] ) ) { + $passwords[ $i ]['uuid'] = wp_generate_uuid4(); + $save = true; + } + } + + if ( $save ) { + static::set_user_application_passwords( $user_id, $passwords ); + } + + return $passwords; + } + + /** + * Gets a user's application password with the given uuid. + * + * @since 5.6.0 + * + * @param int $user_id User ID. + * @param string $uuid The password's uuid. + * @return array|null The application password if found, null otherwise. + */ + public static function get_user_application_password( $user_id, $uuid ) { + $passwords = static::get_user_application_passwords( $user_id ); + + foreach ( $passwords as $password ) { + if ( $password['uuid'] === $uuid ) { + return $password; + } + } + + return null; + } + + /** + * Updates an application password. + * + * @since 5.6.0 + * + * @param int $user_id User ID. + * @param string $uuid The password's uuid. + * @param array $update Information about the application password to update. + * @return true|WP_Error True if successful, otherwise a WP_Error instance is returned on error. + */ + public static function update_application_password( $user_id, $uuid, $update = array() ) { + $passwords = static::get_user_application_passwords( $user_id ); + + foreach ( $passwords as &$item ) { + if ( $item['uuid'] !== $uuid ) { + continue; + } + + $save = false; + + if ( ! empty( $update['name'] ) && $item['name'] !== $update['name'] ) { + $item['name'] = $update['name']; + $save = true; + } + + if ( $save ) { + $saved = static::set_user_application_passwords( $user_id, $passwords ); + + if ( ! $saved ) { + return new WP_Error( 'db_error', __( 'Could not save application password.' ) ); + } + } + + /** + * Fires when an application password is updated. + * + * @since 5.6.0 + * + * @param int $user_id The user ID. + * @param array $item The updated app password details. + * @param array $update The information to update. + */ + do_action( 'wp_update_application_password', $user_id, $item, $update ); + + return true; + } + + return new WP_Error( 'application_password_not_found', __( 'Could not find an application password with that id.' ) ); + } + + /** + * Records that an application password has been used. + * + * @since 5.6.0 + * + * @param int $user_id User ID. + * @param string $uuid The password's uuid. + * @return true|WP_Error True if the usage was recorded, a WP_Error if an error occurs. + */ + public static function record_application_password_usage( $user_id, $uuid ) { + $passwords = static::get_user_application_passwords( $user_id ); + + foreach ( $passwords as &$password ) { + if ( $password['uuid'] !== $uuid ) { + continue; + } + + // Only record activity once a day. + if ( $password['last_used'] + DAY_IN_SECONDS > time() ) { + continue; + } + + $password['last_used'] = time(); + $password['last_ip'] = $_SERVER['REMOTE_ADDR']; + + $saved = static::set_user_application_passwords( $user_id, $passwords ); + + if ( ! $saved ) { + return new WP_Error( 'db_error', __( 'Could not save application password.' ) ); + } + + return true; + } + + // Specified Application Password not found! + return new WP_Error( 'application_password_not_found', __( 'Could not find an application password with that id.' ) ); + } + + /** + * Deletes an application password. + * + * @since 5.6.0 + * + * @param int $user_id User ID. + * @param string $uuid The password's uuid. + * @return true|WP_Error Whether the password was successfully found and deleted, a WP_Error otherwise. + */ + public static function delete_application_password( $user_id, $uuid ) { + $passwords = static::get_user_application_passwords( $user_id ); + + foreach ( $passwords as $key => $item ) { + if ( $item['uuid'] === $uuid ) { + unset( $passwords[ $key ] ); + $saved = static::set_user_application_passwords( $user_id, $passwords ); + + if ( ! $saved ) { + return new WP_Error( 'db_error', __( 'Could not delete application password.' ) ); + } + + /** + * Fires when an application password is deleted. + * + * @since 5.6.0 + * + * @param int $user_id The user ID. + * @param array $item The data about the application password. + */ + do_action( 'wp_delete_application_password', $user_id, $item ); + + return true; + } + } + + return new WP_Error( 'application_password_not_found', __( 'Could not find an application password with that id.' ) ); + } + + /** + * Deletes all application passwords for the given user. + * + * @since 5.6.0 + * + * @param int $user_id User ID. + * @return int|WP_Error The number of passwords that were deleted or a WP_Error on failure. + */ + public static function delete_all_application_passwords( $user_id ) { + $passwords = static::get_user_application_passwords( $user_id ); + + if ( $passwords ) { + $saved = static::set_user_application_passwords( $user_id, array() ); + + if ( ! $saved ) { + return new WP_Error( 'db_error', __( 'Could not delete application passwords.' ) ); + } + + foreach ( $passwords as $item ) { + /** This action is documented in wp-includes/class-wp-application-passwords.php */ + do_action( 'wp_delete_application_password', $user_id, $item ); + } + + return count( $passwords ); + } + + return 0; + } + + /** + * Sets a users application passwords. + * + * @since 5.6.0 + * + * @param int $user_id User ID. + * @param array $passwords Application passwords. + * + * @return bool + */ + protected static function set_user_application_passwords( $user_id, $passwords ) { + return update_user_meta( $user_id, static::USERMETA_KEY_APPLICATION_PASSWORDS, $passwords ); + } + + /** + * Sanitizes and then splits a password into smaller chunks. + * + * @since 5.6.0 + * + * @param string $raw_password The raw application password. + * @return string The chunked password. + */ + public static function chunk_password( $raw_password ) { + $raw_password = preg_replace( '/[^a-z\d]/i', '', $raw_password ); + + return trim( chunk_split( $raw_password, 4, ' ' ) ); + } +} diff --git a/wp-includes/class-wp-rewrite.php b/wp-includes/class-wp-rewrite.php index 95a0386706..c5de10bb6d 100644 --- a/wp-includes/class-wp-rewrite.php +++ b/wp-includes/class-wp-rewrite.php @@ -1509,6 +1509,7 @@ class WP_Rewrite { $rules = "\n"; $rules .= "RewriteEngine On\n"; + $rules .= 'RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]'; $rules .= "RewriteBase $home_root\n"; // Prevent -f checks on index.php. diff --git a/wp-includes/default-filters.php b/wp-includes/default-filters.php index 4275d81677..dbc4fefb09 100644 --- a/wp-includes/default-filters.php +++ b/wp-includes/default-filters.php @@ -276,6 +276,9 @@ add_action( 'auth_cookie_expired', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_bad_username', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_bad_hash', 'rest_cookie_collect_status' ); add_action( 'auth_cookie_valid', 'rest_cookie_collect_status' ); +add_action( 'application_password_failed_authentication', 'rest_application_password_collect_status' ); +add_action( 'application_password_did_authenticate', 'rest_application_password_collect_status' ); +add_filter( 'rest_authentication_errors', 'rest_application_password_check_errors', 90 ); add_filter( 'rest_authentication_errors', 'rest_cookie_check_errors', 100 ); // Actions. @@ -427,9 +430,11 @@ add_filter( 'heartbeat_nopriv_send', 'wp_auth_check' ); // Default authentication filters. add_filter( 'authenticate', 'wp_authenticate_username_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_email_password', 20, 3 ); +add_filter( 'authenticate', 'wp_authenticate_application_password', 20, 3 ); add_filter( 'authenticate', 'wp_authenticate_spam_check', 99 ); add_filter( 'determine_current_user', 'wp_validate_auth_cookie' ); add_filter( 'determine_current_user', 'wp_validate_logged_in_cookie', 20 ); +add_filter( 'determine_current_user', 'wp_validate_application_password', 20 ); // Split term updates. add_action( 'admin_init', '_wp_check_for_scheduled_split_terms' ); diff --git a/wp-includes/load.php b/wp-includes/load.php index 702459d601..34f30bcb03 100644 --- a/wp-includes/load.php +++ b/wp-includes/load.php @@ -86,6 +86,47 @@ function wp_fix_server_vars() { $_SERVER['PHP_SELF'] = preg_replace( '/(\?.*)?$/', '', $_SERVER['REQUEST_URI'] ); $PHP_SELF = $_SERVER['PHP_SELF']; } + + wp_populate_basic_auth_from_authorization_header(); +} + +/** + * Populates the Basic Auth server details from the Authorization header. + * + * Some servers running in CGI or FastCGI mode don't pass the Authorization + * header on to WordPress. If it's been rewritten to the `HTTP_AUTHORIZATION` header, + * fill in the proper $_SERVER variables instead. + * + * @since 5.6.0 + */ +function wp_populate_basic_auth_from_authorization_header() { + // If we don't have anything to pull from, return early. + if ( ! isset( $_SERVER['HTTP_AUTHORIZATION'] ) && ! isset( $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ) ) { + return; + } + + // If either PHP_AUTH key is already set, do nothing. + if ( isset( $_SERVER['PHP_AUTH_USER'] ) || isset( $_SERVER['PHP_AUTH_PW'] ) ) { + return; + } + + // From our prior conditional, one of these must be set. + $header = isset( $_SERVER['HTTP_AUTHORIZATION'] ) ? $_SERVER['HTTP_AUTHORIZATION'] : $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + + // Test to make sure the pattern matches expected. + if ( ! preg_match( '%^Basic [a-z\d/+]*={0,2}$%i', $header ) ) { + return; + } + + // Removing `Basic ` the token would start six characters in. + $token = substr( $header, 6 ); + $userpass = base64_decode( $token ); + + list( $user, $pass ) = explode( ':', $userpass ); + + // Now shove them in the proper keys where we're expecting later on. + $_SERVER['PHP_AUTH_USER'] = $user; + $_SERVER['PHP_AUTH_PW'] = $pass; } /** diff --git a/wp-includes/rest-api.php b/wp-includes/rest-api.php index aed8a10491..0da4aeae36 100644 --- a/wp-includes/rest-api.php +++ b/wp-includes/rest-api.php @@ -209,6 +209,7 @@ function rest_api_default_filters() { add_filter( 'rest_post_dispatch', 'rest_filter_response_fields', 10, 3 ); add_filter( 'rest_pre_dispatch', 'rest_handle_options_request', 10, 3 ); + add_filter( 'rest_index', 'rest_add_application_passwords_to_index' ); } /** @@ -264,6 +265,10 @@ function create_initial_rest_routes() { $controller = new WP_REST_Users_Controller; $controller->register_routes(); + // Application Passwords + $controller = new WP_REST_Application_Passwords_Controller(); + $controller->register_routes(); + // Comments. $controller = new WP_REST_Comments_Controller; $controller->register_routes(); @@ -310,7 +315,6 @@ function create_initial_rest_routes() { // Block Directory. $controller = new WP_REST_Block_Directory_Controller(); $controller->register_routes(); - } /** @@ -1034,6 +1038,80 @@ function rest_cookie_collect_status() { $wp_rest_auth_cookie = true; } +/** + * Collects the status of authenticating with an application password. + * + * @since 5.6.0 + * + * @global WP_User|WP_Error|null $wp_rest_application_password_status + * + * @param WP_Error $user_or_error The authenticated user or error instance. + */ +function rest_application_password_collect_status( $user_or_error ) { + global $wp_rest_application_password_status; + + $wp_rest_application_password_status = $user_or_error; +} + +/** + * Checks for errors when using application password-based authentication. + * + * @since 5.6.0 + * + * @global WP_User|WP_Error|null $wp_rest_application_password_status + * + * @param WP_Error|null|true $result Error from another authentication handler, + * null if we should handle it, or another value if not. + * @return WP_Error|null|true WP_Error if the application password is invalid, the $result, otherwise true. + */ +function rest_application_password_check_errors( $result ) { + global $wp_rest_application_password_status; + + if ( ! empty( $result ) ) { + return $result; + } + + if ( is_wp_error( $wp_rest_application_password_status ) ) { + $data = $wp_rest_application_password_status->get_error_data(); + + if ( ! isset( $data['status'] ) ) { + $data['status'] = 401; + } + + $wp_rest_application_password_status->add_data( $data ); + + return $wp_rest_application_password_status; + } + + if ( $wp_rest_application_password_status instanceof WP_User ) { + return true; + } + + return $result; +} + +/** + * Adds Application Passwords info to the REST API index. + * + * @since 5.6.0 + * + * @param WP_REST_Response $response The index response object. + * @return WP_REST_Response + */ +function rest_add_application_passwords_to_index( $response ) { + if ( ! wp_is_application_passwords_available() ) { + return $response; + } + + $response->data['authentication']['application-passwords'] = array( + 'endpoints' => array( + 'authorization' => admin_url( 'authorize-application.php' ), + ), + ); + + return $response; +} + /** * Retrieves the avatar urls in various sizes. * diff --git a/wp-includes/rest-api/class-wp-rest-server.php b/wp-includes/rest-api/class-wp-rest-server.php index 20b029fca2..4c0555d3ad 100644 --- a/wp-includes/rest-api/class-wp-rest-server.php +++ b/wp-includes/rest-api/class-wp-rest-server.php @@ -223,11 +223,30 @@ class WP_REST_Server { * * @see WP_REST_Server::dispatch() * + * @global WP_User $current_user The currently authenticated user. + * * @param string $path Optional. The request route. If not set, `$_SERVER['PATH_INFO']` will be used. * Default null. * @return null|false Null if not served and a HEAD request, false otherwise. */ public function serve_request( $path = null ) { + /* @var WP_User|null $current_user */ + global $current_user; + + if ( $current_user instanceof WP_User && ! $current_user->exists() ) { + /* + * If there is no current user authenticated via other means, clear + * the cached lack of user, so that an authenticate check can set it + * properly. + * + * This is done because for authentications such as Application + * Passwords, we don't want it to be accepted unless the current HTTP + * request is an API request, which can't always be identified early + * enough in evaluation. + */ + $current_user = null; + } + $content_type = isset( $_GET['_jsonp'] ) ? 'application/javascript' : 'application/json'; $this->send_header( 'Content-Type', $content_type . '; charset=' . get_option( 'blog_charset' ) ); $this->send_header( 'X-Robots-Tag', 'noindex' ); diff --git a/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php b/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php new file mode 100644 index 0000000000..fdb496b412 --- /dev/null +++ b/wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php @@ -0,0 +1,656 @@ +namespace = 'wp/v2'; + $this->rest_base = 'users/(?P(?:[\d]+|me))/application-passwords'; + } + + /** + * Registers the REST API routes for the application passwords controller. + * + * @since 5.6.0 + */ + public function register_routes() { + register_rest_route( + $this->namespace, + '/' . $this->rest_base, + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_items' ), + 'permission_callback' => array( $this, 'get_items_permissions_check' ), + 'args' => $this->get_collection_params(), + ), + array( + 'methods' => WP_REST_Server::CREATABLE, + 'callback' => array( $this, 'create_item' ), + 'permission_callback' => array( $this, 'create_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema(), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_items' ), + 'permission_callback' => array( $this, 'delete_items_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + + register_rest_route( + $this->namespace, + '/' . $this->rest_base . '/(?P[\w\-]+)', + array( + array( + 'methods' => WP_REST_Server::READABLE, + 'callback' => array( $this, 'get_item' ), + 'permission_callback' => array( $this, 'get_item_permissions_check' ), + 'args' => array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ), + ), + array( + 'methods' => WP_REST_Server::EDITABLE, + 'callback' => array( $this, 'update_item' ), + 'permission_callback' => array( $this, 'update_item_permissions_check' ), + 'args' => $this->get_endpoint_args_for_item_schema( WP_REST_Server::EDITABLE ), + ), + array( + 'methods' => WP_REST_Server::DELETABLE, + 'callback' => array( $this, 'delete_item' ), + 'permission_callback' => array( $this, 'delete_item_permissions_check' ), + ), + 'schema' => array( $this, 'get_public_item_schema' ), + ) + ); + } + + /** + * Checks if a given request has access to get application passwords. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access, WP_Error object otherwise. + */ + public function get_items_permissions_check( $request ) { + return $this->do_permissions_check( $request ); + } + + /** + * Retrieves a collection of application passwords. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_items( $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + $passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID ); + $response = array(); + + foreach ( $passwords as $password ) { + $response[] = $this->prepare_response_for_collection( + $this->prepare_item_for_response( $password, $request ) + ); + } + + return new WP_REST_Response( $response ); + } + + /** + * Checks if a given request has access to get a specific application password. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has read access for the item, WP_Error object otherwise. + */ + public function get_item_permissions_check( $request ) { + return $this->do_permissions_check( $request ); + } + + /** + * Retrieves one application password from the collection. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function get_item( $request ) { + $password = $this->get_application_password( $request ); + + if ( is_wp_error( $password ) ) { + return $password; + } + + return $this->prepare_item_for_response( $password, $request ); + } + + /** + * Checks if a given request has access to create application passwords. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. + */ + public function create_item_permissions_check( $request ) { + return $this->do_permissions_check( $request ); + } + + /** + * Creates an application password. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function create_item( $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + $prepared = $this->prepare_item_for_database( $request ); + + if ( is_wp_error( $prepared ) ) { + return $prepared; + } + + $created = WP_Application_Passwords::create_new_application_password( $user->ID, wp_slash( (array) $prepared ) ); + + if ( is_wp_error( $created ) ) { + return $created; + } + + $password = $created[0]; + $item = WP_Application_Passwords::get_user_application_password( $user->ID, $created[1]['uuid'] ); + + $item['new_password'] = WP_Application_Passwords::chunk_password( $password ); + $fields_update = $this->update_additional_fields_for_object( $item, $request ); + + if ( is_wp_error( $fields_update ) ) { + return $fields_update; + } + + /** + * Fires after a single application password is completely created or updated via the REST API. + * + * @since 5.6.0 + * + * @param array $item Inserted or updated password item. + * @param WP_REST_Request $request Request object. + * @param bool $creating True when creating an application password, false when updating. + */ + do_action( 'rest_after_insert_application_password', $item, $request, true ); + + $request->set_param( 'context', 'edit' ); + $response = $this->prepare_item_for_response( $item, $request ); + + $response->set_status( 201 ); + $response->header( 'Location', $response->get_links()['self'][0]['href'] ); + + return $response; + } + + /** + * Checks if a given request has access to update application passwords. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to create items, WP_Error object otherwise. + */ + public function update_item_permissions_check( $request ) { + return $this->do_permissions_check( $request ); + } + + /** + * Updates an application password. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function update_item( $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + $item = $this->get_application_password( $request ); + + if ( is_wp_error( $item ) ) { + return $item; + } + + $prepared = $this->prepare_item_for_database( $request ); + + if ( is_wp_error( $prepared ) ) { + return $prepared; + } + + $saved = WP_Application_Passwords::update_application_password( $user->ID, $item['uuid'], wp_slash( (array) $prepared ) ); + + if ( is_wp_error( $saved ) ) { + return $saved; + } + + $fields_update = $this->update_additional_fields_for_object( $item, $request ); + + if ( is_wp_error( $fields_update ) ) { + return $fields_update; + } + + $item = WP_Application_Passwords::get_user_application_password( $user->ID, $item['uuid'] ); + + /** This action is documented in wp-includes/rest-api/endpoints/class-wp-rest-application-passwords-controller.php */ + do_action( 'rest_after_insert_application_password', $item, $request, false ); + + $request->set_param( 'context', 'edit' ); + return $this->prepare_item_for_response( $item, $request ); + } + + /** + * Checks if a given request has access to delete all application passwords. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. + */ + public function delete_items_permissions_check( $request ) { + return $this->do_permissions_check( $request ); + } + + /** + * Deletes all application passwords. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function delete_items( $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + $deleted = WP_Application_Passwords::delete_all_application_passwords( $user->ID ); + + if ( is_wp_error( $deleted ) ) { + return $deleted; + } + + return new WP_REST_Response( + array( + 'deleted' => true, + 'count' => $deleted, + ) + ); + } + + /** + * Checks if a given request has access to delete a specific application password. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return true|WP_Error True if the request has access to delete the item, WP_Error object otherwise. + */ + public function delete_item_permissions_check( $request ) { + return $this->do_permissions_check( $request ); + } + + /** + * Deletes one application password. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Full details about the request. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function delete_item( $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + $password = $this->get_application_password( $request ); + + if ( is_wp_error( $password ) ) { + return $password; + } + + $request->set_param( 'context', 'edit' ); + $previous = $this->prepare_item_for_response( $password, $request ); + $deleted = WP_Application_Passwords::delete_application_password( $user->ID, $password['uuid'] ); + + if ( is_wp_error( $deleted ) ) { + return $deleted; + } + + return new WP_REST_Response( + array( + 'deleted' => true, + 'previous' => $previous->get_data(), + ) + ); + } + + /** + * Performs a permissions check for the request. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request + * @return true|WP_Error + */ + protected function do_permissions_check( $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + if ( ! current_user_can( 'edit_user', $user->ID ) ) { + return new WP_Error( + 'rest_cannot_manage_application_passwords', + __( 'Sorry, you are not allowed to manage application passwords for this user.' ), + array( 'status' => rest_authorization_required_code() ) + ); + } + + return true; + } + + /** + * Prepares an application password for a create or update operation. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request Request object. + * @return object|WP_Error The prepared item, or WP_Error object on failure. + */ + protected function prepare_item_for_database( $request ) { + $prepared = (object) array( + 'name' => $request['name'], + ); + + /** + * Filters an application password before it is inserted via the REST API. + * + * @since 5.6.0 + * + * @param stdClass $prepared An object representing a single application password prepared for inserting or updating the database. + * @param WP_REST_Request $request Request object. + */ + return apply_filters( 'rest_pre_insert_application_password', $prepared, $request ); + } + + /** + * Prepares the application password for the REST response. + * + * @since 5.6.0 + * + * @param array $item WordPress representation of the item. + * @param WP_REST_Request $request Request object. + * @return WP_REST_Response|WP_Error Response object on success, or WP_Error object on failure. + */ + public function prepare_item_for_response( $item, $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + $prepared = array( + 'uuid' => $item['uuid'], + 'name' => $item['name'], + 'created' => gmdate( 'Y-m-d\TH:i:s', $item['created'] ), + 'last_used' => $item['last_used'] ? gmdate( 'Y-m-d\TH:i:s', $item['last_used'] ) : null, + 'last_ip' => $item['last_ip'] ? $item['last_ip'] : null, + ); + + if ( isset( $item['new_password'] ) ) { + $prepared['password'] = $item['new_password']; + } + + $prepared = $this->add_additional_fields_to_object( $prepared, $request ); + $prepared = $this->filter_response_by_context( $prepared, $request['context'] ); + + $response = new WP_REST_Response( $prepared ); + $response->add_links( $this->prepare_links( $user, $item ) ); + + /** + * Filters the REST API response for an application password. + * + * @since 5.6.0 + * + * @param WP_REST_Response $response The response object. + * @param array $item The application password array. + * @param WP_REST_Request $request The request object. + */ + return apply_filters( 'rest_prepare_application_password', $response, $item, $request ); + } + + /** + * Prepares links for the object. + * + * @since 5.6.0 + * + * @param WP_User $user The requested user. + * @param array $item The application password. + * @return array The list of links. + */ + protected function prepare_links( WP_User $user, $item ) { + return array( + 'self' => array( + 'href' => rest_url( sprintf( '%s/users/%d/application-passwords/%s', $this->namespace, $user->ID, $item['uuid'] ) ), + ), + ); + } + + /** + * Gets the requested user. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request The request object. + * @return WP_User|WP_Error The WordPress user associated with the request, or a WP_Error if none found. + */ + protected function get_user( $request ) { + if ( ! wp_is_application_passwords_available() ) { + return new WP_Error( + 'application_passwords_disabled', + __( 'Application passwords are not enabled.' ), + array( 'status' => 500 ) + ); + } + + $error = new WP_Error( + 'rest_user_invalid_id', + __( 'Invalid user ID.' ), + array( 'status' => 404 ) + ); + + $id = $request['user_id']; + + if ( 'me' === $id ) { + if ( ! is_user_logged_in() ) { + return new WP_Error( + 'rest_not_logged_in', + __( 'You are not currently logged in.' ), + array( 'status' => 401 ) + ); + } + + $user = wp_get_current_user(); + } else { + $id = (int) $id; + + if ( $id <= 0 ) { + return $error; + } + + $user = get_userdata( $id ); + } + + if ( empty( $user ) || ! $user->exists() ) { + return $error; + } + + if ( is_multisite() && ! is_user_member_of_blog( $user->ID ) ) { + return $error; + } + + if ( ! wp_is_application_passwords_available_for_user( $user ) ) { + return new WP_Error( + 'application_passwords_disabled_for_user', + __( 'Application passwords are not enabled for your account. Please contact the site administrator for assistance.' ), + array( 'status' => 500 ) + ); + } + + return $user; + } + + /** + * Gets the requested application password. + * + * @since 5.6.0 + * + * @param WP_REST_Request $request The request object. + * @return array|WP_Error The application password details if found, a WP_Error otherwise. + */ + protected function get_application_password( $request ) { + $user = $this->get_user( $request ); + + if ( is_wp_error( $user ) ) { + return $user; + } + + $password = WP_Application_Passwords::get_user_application_password( $user->ID, $request['uuid'] ); + + if ( ! $password ) { + return new WP_Error( + 'rest_application_password_not_found', + __( 'Application password not found.' ), + array( 'status' => 404 ) + ); + } + + return $password; + } + + /** + * Retrieves the query params for the collections. + * + * @since 5.6.0 + * + * @return array Query parameters for the collection. + */ + public function get_collection_params() { + return array( + 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + ); + } + + /** + * Retrieves the application password's schema, conforming to JSON Schema. + * + * @since 5.6.0 + * + * @return array Item schema data. + */ + public function get_item_schema() { + if ( $this->schema ) { + return $this->add_additional_fields_schema( $this->schema ); + } + + $this->schema = array( + '$schema' => 'http://json-schema.org/draft-04/schema#', + 'title' => 'application-password', + 'type' => 'object', + 'properties' => array( + 'uuid' => array( + 'description' => __( 'The unique identifier for the application password.' ), + 'type' => 'string', + 'format' => 'uuid', + 'context' => array( 'view', 'edit', 'embed' ), + 'readonly' => true, + ), + 'name' => array( + 'description' => __( 'The name of the application password.' ), + 'type' => 'string', + 'required' => true, + 'context' => array( 'view', 'edit', 'embed' ), + ), + 'password' => array( + 'description' => __( 'The generated password. Only available after adding an application.' ), + 'type' => 'string', + 'context' => array( 'edit' ), + 'readonly' => true, + ), + 'created' => array( + 'description' => __( 'The GMT date the application password was created.' ), + 'type' => 'string', + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'last_used' => array( + 'description' => __( 'The GMT date the application password was last used.' ), + 'type' => array( 'string', 'null' ), + 'format' => 'date-time', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + 'last_ip' => array( + 'description' => __( 'The IP address the application password was last used by.' ), + 'type' => array( 'string', 'null' ), + 'format' => 'ip', + 'context' => array( 'view', 'edit' ), + 'readonly' => true, + ), + ), + ); + + return $this->add_additional_fields_schema( $this->schema ); + } +} diff --git a/wp-includes/script-loader.php b/wp-includes/script-loader.php index 8d096e344d..9f616ef5e7 100644 --- a/wp-includes/script-loader.php +++ b/wp-includes/script-loader.php @@ -1067,6 +1067,12 @@ function wp_default_scripts( $scripts ) { ); $scripts->set_translations( 'password-strength-meter' ); + $scripts->add( 'application-passwords', "/wp-admin/js/application-passwords$suffix.js", array( 'jquery', 'wp-util', 'wp-api-request', 'wp-date', 'wp-i18n', 'wp-hooks', 'wp-a11y' ), false, 1 ); + $scripts->set_translations( 'application-passwords' ); + + $scripts->add( 'auth-app', "/wp-admin/js/auth-app$suffix.js", array( 'jquery', 'wp-api-request', 'wp-i18n', 'wp-hooks' ), false, 1 ); + $scripts->set_translations( 'auth-app' ); + $scripts->add( 'user-profile', "/wp-admin/js/user-profile$suffix.js", array( 'jquery', 'password-strength-meter', 'wp-util' ), false, 1 ); $scripts->set_translations( 'user-profile' ); diff --git a/wp-includes/user.php b/wp-includes/user.php index d2977e3467..09ca45a710 100644 --- a/wp-includes/user.php +++ b/wp-includes/user.php @@ -297,6 +297,176 @@ function wp_authenticate_cookie( $user, $username, $password ) { return $user; } +/** + * Authenticates the user using an application password. + * + * @since 5.6.0 + * + * @param WP_User|WP_Error|null $input_user WP_User or WP_Error object if a previous + * callback failed authentication. + * @param string $username Username for authentication. + * @param string $password Password for authentication. + * @return WP_User|WP_Error WP_User on success, WP_Error on failure. + */ +function wp_authenticate_application_password( $input_user, $username, $password ) { + if ( $input_user instanceof WP_User ) { + return $input_user; + } + + $is_api_request = ( ( defined( 'XMLRPC_REQUEST' ) && XMLRPC_REQUEST ) || ( defined( 'REST_REQUEST' ) && REST_REQUEST ) ); + + /** + * Filters whether this is an API request that Application Passwords can be used on. + * + * By default, Application Passwords is available for the REST API and XML-RPC. + * + * @since 5.6.0 + * + * @param bool $is_api_request If this is an acceptable API request. + */ + $is_api_request = apply_filters( 'application_password_is_api_request', $is_api_request ); + + if ( ! $is_api_request ) { + return $input_user; + } + + $error = null; + $user = get_user_by( 'login', $username ); + + if ( ! $user && is_email( $username ) ) { + $user = get_user_by( 'email', $username ); + } + + // If the login name is invalid, short circuit. + if ( ! $user ) { + if ( is_email( $username ) ) { + $error = new WP_Error( + 'invalid_email', + __( 'Unknown email address. Check again or try your username.' ) + ); + } else { + $error = new WP_Error( + 'invalid_username', + __( 'Unknown username. Check again or try your email address.' ) + ); + } + } elseif ( ! wp_is_application_passwords_available_for_user( $user ) ) { + $error = new WP_Error( + 'application_passwords_disabled', + __( 'Application passwords are disabled for the requested user.' ) + ); + } + + if ( $error ) { + /** + * Fires when an application password failed to authenticate the user. + * + * @since 5.6.0 + * + * @param WP_Error $error The authentication error. + */ + do_action( 'application_password_failed_authentication', $error ); + + return $error; + } + + /* + * Strip out anything non-alphanumeric. This is so passwords can be used with + * or without spaces to indicate the groupings for readability. + * + * Generated application passwords are exclusively alphanumeric. + */ + $password = preg_replace( '/[^a-z\d]/i', '', $password ); + + $hashed_passwords = WP_Application_Passwords::get_user_application_passwords( $user->ID ); + + foreach ( $hashed_passwords as $key => $item ) { + if ( ! wp_check_password( $password, $item['password'], $user->ID ) ) { + continue; + } + + $error = new WP_Error(); + + /** + * Fires when an application password has been successfully checked as valid. + * + * This allows for plugins to add additional constraints to prevent an application password from being used. + * + * @since 5.6.0 + * + * @param WP_Error $error The error object. + * @param WP_User $user The user authenticating. + * @param array $item The details about the application password. + * @param string $password The raw supplied password. + */ + do_action( 'wp_authenticate_application_password_errors', $error, $user, $item, $password ); + + if ( is_wp_error( $error ) && $error->has_errors() ) { + /** This action is documented in wp-includes/user.php */ + do_action( 'application_password_failed_authentication', $error ); + + return $error; + } + + WP_Application_Passwords::record_application_password_usage( $user->ID, $item['uuid'] ); + + /** + * Fires after an application password was used for authentication. + * + * @since 5.6.0 + * + * @param WP_User $user The user who was authenticated. + * @param array $item The application password used. + */ + do_action( 'application_password_did_authenticate', $user, $item ); + + return $user; + } + + $error = new WP_Error( + 'incorrect_password', + __( 'The provided password is an invalid application password.' ) + ); + + /** This action is documented in wp-includes/user.php */ + do_action( 'application_password_failed_authentication', $error ); + + return $error; +} + +/** + * Validates the application password credentials passed via Basic Authentication. + * + * @since 5.6.0 + * + * @param int|bool $input_user User ID if one has been determined, false otherwise. + * @return int|bool The authenticated user ID if successful, false otherwise. + */ +function wp_validate_application_password( $input_user ) { + // Don't authenticate twice. + if ( ! empty( $input_user ) ) { + return $input_user; + } + + if ( ! wp_is_application_passwords_available() ) { + return $input_user; + } + + // Check that we're trying to authenticate + if ( ! isset( $_SERVER['PHP_AUTH_USER'] ) ) { + return $input_user; + } + + $authenticated = wp_authenticate_application_password( null, $_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW'] ); + + if ( $authenticated instanceof WP_User ) { + return $authenticated->ID; + } + + // If it wasn't a user what got returned, just pass on what we had received originally. + return $input_user; +} + /** * For Multisite blogs, check if the authenticated user has been marked as a * spammer, or if the user's primary blog has been marked as spam. @@ -3923,3 +4093,59 @@ function wp_get_user_request( $request_id ) { return new WP_User_Request( $post ); } + +/** + * Checks if Application Passwords is globally available. + * + * By default, Application Passwords is available to all sites using SSL, but this function is + * filterable to adjust its availability. + * + * @since 5.6.0 + * + * @return bool + */ +function wp_is_application_passwords_available() { + /** + * Filters whether Application Passwords is available. + * + * @since 5.6.0 + * + * @param bool $available True if available, false otherwise. + */ + return apply_filters( 'wp_is_application_passwords_available', is_ssl() ); +} + +/** + * Checks if Application Passwords is enabled for a specific user. + * + * By default all users can use Application Passwords, but this function is filterable to restrict + * availability to certain users. + * + * @since 5.6.0 + * + * @param int|WP_User $user The user to check. + * @return bool + */ +function wp_is_application_passwords_available_for_user( $user ) { + if ( ! wp_is_application_passwords_available() ) { + return false; + } + + if ( ! is_object( $user ) ) { + $user = get_userdata( $user ); + } + + if ( ! $user || ! $user->exists() ) { + return false; + } + + /** + * Filters whether Application Passwords is available for a specific user. + * + * @since 5.6.0 + * + * @param bool $available True if available, false otherwise. + * @param WP_User $user The user to check. + */ + return apply_filters( 'wp_is_application_passwords_available_for_user', true, $user ); +} diff --git a/wp-includes/version.php b/wp-includes/version.php index 6e5209875c..4526b546aa 100644 --- a/wp-includes/version.php +++ b/wp-includes/version.php @@ -13,7 +13,7 @@ * * @global string $wp_version */ -$wp_version = '5.6-alpha-49108'; +$wp_version = '5.6-alpha-49109'; /** * Holds the WordPress DB revision, increments when changes are made to the WordPress DB schema. diff --git a/wp-login.php b/wp-login.php index 0135ecffb7..0ad2c039a8 100644 --- a/wp-login.php +++ b/wp-login.php @@ -1371,6 +1371,19 @@ switch ( $action ) { $errors->add( 'updated', __( 'You have successfully updated WordPress! Please log back in to see what’s new.' ), 'message' ); } elseif ( WP_Recovery_Mode_Link_Service::LOGIN_ACTION_ENTERED === $action ) { $errors->add( 'enter_recovery_mode', __( 'Recovery Mode Initialized. Please log in to continue.' ), 'message' ); + } elseif ( isset( $_GET['redirect_to'] ) && false !== strpos( $_GET['redirect_to'], 'wp-admin/authorize-application.php' ) ) { + $query_component = wp_parse_url( $_GET['redirect_to'], PHP_URL_QUERY ); + parse_str( $query_component, $query ); + + if ( ! empty( $query['app_name'] ) ) { + /* translators: 1: Website name, 2: Application name. */ + $message = sprintf( 'Please log in to %1$s to authorize %2$s to connect to your account.', get_bloginfo( 'name', 'display' ), '' . esc_html( $query['app_name'] ) . '' ); + } else { + /* translators: Website name. */ + $message = sprintf( 'Please log in to %s to proceed with authorization.', get_bloginfo( 'name', 'display' ) ); + } + + $errors->add( 'authorize_application', $message, 'message' ); } } diff --git a/wp-settings.php b/wp-settings.php index e21e51999b..f777b60a5e 100644 --- a/wp-settings.php +++ b/wp-settings.php @@ -236,6 +236,7 @@ require ABSPATH . WPINC . '/class-wp-widget-factory.php'; require ABSPATH . WPINC . '/nav-menu.php'; require ABSPATH . WPINC . '/nav-menu-template.php'; require ABSPATH . WPINC . '/admin-bar.php'; +require ABSPATH . WPINC . '/class-wp-application-passwords.php'; require ABSPATH . WPINC . '/rest-api.php'; require ABSPATH . WPINC . '/rest-api/class-wp-rest-server.php'; require ABSPATH . WPINC . '/rest-api/class-wp-rest-response.php'; @@ -259,6 +260,7 @@ require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-settings-controller require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-themes-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-plugins-controller.php'; require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-block-directory-controller.php'; +require ABSPATH . WPINC . '/rest-api/endpoints/class-wp-rest-application-passwords-controller.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-meta-fields.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-comment-meta-fields.php'; require ABSPATH . WPINC . '/rest-api/fields/class-wp-rest-post-meta-fields.php';