diff --git a/src/js/_enqueues/admin/auth-app.js b/src/js/_enqueues/admin/auth-app.js index 99478d1824a2d..d0b68ea5fca4c 100644 --- a/src/js/_enqueues/admin/auth-app.js +++ b/src/js/_enqueues/admin/auth-app.js @@ -2,7 +2,7 @@ * @output wp-admin/js/auth-app.js */ -/* global authApp */ +/* global authApp, ClipboardJS */ ( function( $, authApp ) { var $appNameField = $( '#app_name' ), @@ -11,10 +11,26 @@ $form = $appNameField.closest( 'form' ), context = { userLogin: authApp.user_login, - successUrl: authApp.success, - rejectUrl: authApp.reject + successUrl: authApp.success }; + // If redirecting to an external site, gate the approve button behind the confirmation checkbox. + if ( authApp.successHost ) { + var $checkbox = $( 'input[name="confirm_external_redirect"]' ); + + // Start the approve button in a disabled state. + $approveBtn.prop( 'aria-disabled', true ).addClass( 'disabled' ); + + // Toggle the approve button when the checkbox state changes. + $checkbox.on( 'change', function() { + if ( $checkbox.prop( 'checked' ) ) { + $approveBtn.removeProp( 'aria-disabled' ).removeClass( 'disabled' ); + } else { + $approveBtn.prop( 'aria-disabled', true ).addClass( 'disabled' ); + } + } ); + } + $approveBtn.on( 'click', function( e ) { var name = $appNameField.val(), appId = $( 'input[name="app_id"]', $form ).val(); @@ -25,8 +41,8 @@ return; } - if ( 0 === name.length ) { - $appNameField.trigger( 'focus' ); + if ( ! $form[ 0 ].checkValidity() ) { + $form[ 0 ].reportValidity(); return; } @@ -44,12 +60,12 @@ * Filters the request data used to Authorize an Application Password request. * * @since 5.6.0 + * @since x.y.z A reject URL is no longer supported or used. * * @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 ); @@ -89,20 +105,43 @@ /* translators: %s: Application name. */ '', '' - ) + ' '; + ); $notice = $( '
' ) .attr( 'role', 'alert' ) .attr( 'tabindex', -1 ) .addClass( 'notice notice-success notice-alt' ) .append( $( '' ).addClass( 'application-password-display' ).html( message ) ) + .append( + $( '' ) + .addClass( 'application-password-display' ) + .append( '' ) + .append( ' ' ) + .append( '' ) + ) .append( '' + wp.i18n.__( 'Be sure to save this in a safe location. You will not be able to retrieve it.' ) + '
' ); // We're using .text() to write the variables to avoid any chance of XSS. $( 'strong', $notice ).text( response.name ); $( 'input', $notice ).val( response.password ); + $( '.copy-button', $notice ).attr( 'data-clipboard-text', response.password ); $form.replaceWith( $notice ); $notice.trigger( 'focus' ); + + // Initialize clipboard functionality for the copy button. + var clipboard = new ClipboardJS( '.copy-button' ); + clipboard.on( 'success', function( e ) { + var $successElement = $( '.success', $( e.trigger ).parent() ); + + e.clearSelection(); + $successElement.removeClass( 'hidden' ); + + setTimeout( function() { + $successElement.addClass( 'hidden' ); + }, 3000 ); + + wp.a11y.speak( wp.i18n.__( 'Application password has been copied to your clipboard.' ) ); + } ); } } ).fail( function( jqXHR, textStatus, errorThrown ) { var errorMessage = errorThrown, @@ -147,16 +186,22 @@ * Fires when an Authorize Application Password request has been rejected by the user. * * @since 5.6.0 + * @since x.y.z A reject URL is no longer supported or used. * * @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; + var $notice = $( '' ) + .attr( 'role', 'alert' ) + .attr( 'tabindex', -1 ) + .addClass( 'notice notice-info' ) + .append( $( '' ).text( wp.i18n.__( 'You have not approved this connection. No data has been shared with the application.' ) ) ); + + $form.replaceWith( $notice ); + $notice.trigger( 'focus' ); } ); $form.on( 'submit', function( e ) { diff --git a/src/wp-admin/authorize-application.php b/src/wp-admin/authorize-application.php index 8d931f46666a2..2bad15f7f7f3c 100644 --- a/src/wp-admin/authorize-application.php +++ b/src/wp-admin/authorize-application.php @@ -17,17 +17,12 @@ check_admin_referer( 'authorize_application_password' ); $success_url = $_POST['success_url']; - $reject_url = $_POST['reject_url']; $app_name = $_POST['app_name']; $app_id = $_POST['app_id']; $redirect = ''; if ( isset( $_POST['reject'] ) ) { - if ( $reject_url ) { - $redirect = $reject_url; - } else { - $redirect = admin_url(); - } + $redirect = admin_url(); } elseif ( isset( $_POST['approve'] ) ) { $created = WP_Application_Passwords::create_new_application_password( get_current_user_id(), @@ -56,7 +51,7 @@ } if ( $redirect ) { - // Explicitly not using wp_safe_redirect b/c sends to arbitrary domain. + // Explicitly not using wp_safe_redirect b/c sends to arbitrary domain or custom scheme URL. wp_redirect( $redirect ); exit; } @@ -69,17 +64,9 @@ $app_id = ! empty( $_REQUEST['app_id'] ) ? $_REQUEST['app_id'] : ''; $success_url = ! empty( $_REQUEST['success_url'] ) ? $_REQUEST['success_url'] : null; -if ( ! empty( $_REQUEST['reject_url'] ) ) { - $reject_url = $_REQUEST['reject_url']; -} elseif ( $success_url ) { - $reject_url = add_query_arg( 'success', 'false', $success_url ); -} else { - $reject_url = null; -} - $user = wp_get_current_user(); -$request = compact( 'app_name', 'app_id', 'success_url', 'reject_url' ); +$request = compact( 'app_name', 'app_id', 'success_url' ); $is_valid = wp_is_authorize_application_password_request_valid( $request, $user ); if ( is_wp_error( $is_valid ) ) { @@ -96,7 +83,7 @@ array( 'response' => 501, 'link_text' => __( 'Go Back' ), - 'link_url' => $reject_url ? add_query_arg( 'error', 'disabled', $reject_url ) : admin_url(), + 'link_url' => admin_url(), ) ); } @@ -114,20 +101,27 @@ array( 'response' => 501, 'link_text' => __( 'Go Back' ), - 'link_url' => $reject_url ? add_query_arg( 'error', 'disabled', $reject_url ) : admin_url(), + 'link_url' => admin_url(), ) ); } wp_enqueue_script( 'auth-app' ); + +// Determine how to display the success URL target to the user. +$success_scheme = $success_url ? wp_parse_url( $success_url, PHP_URL_SCHEME ) : ''; +$success_host = $success_url ? wp_parse_url( $success_url, PHP_URL_HOST ) : ''; +$is_custom_scheme = $success_scheme && ! in_array( $success_scheme, array( 'http', 'https' ), true ); +$success_host_display = $is_custom_scheme ? $success_scheme . '://' . $success_host : $success_host; + wp_localize_script( 'auth-app', 'authApp', array( - 'site_url' => site_url(), - 'user_login' => $user->user_login, - 'success' => $success_url, - 'reject' => $reject_url ? $reject_url : admin_url(), + 'site_url' => site_url(), + 'user_login' => $user->user_login, + 'success' => $success_url, + 'successHost' => $success_host, ) ); @@ -150,56 +144,6 @@- ' . esc_html( $app_name ) . '' - ); - ?> -
- - - - - ID, true ); - $blogs_count = count( $blogs ); - - if ( $blogs_count > 1 ) { - ?> -- the %2$s site in this installation that you have permissions on.', - 'This will grant access to all %2$s sites in this installation that you have permissions on.', - $blogs_count - ); - - if ( is_super_admin() ) { - /* translators: 1: URL to my-sites.php, 2: Number of sites the user has. */ - $message = _n( - 'This will grant access to the %2$s site on the network as you have Super Admin rights.', - 'This will grant access to all %2$s sites on the network as you have Super Admin rights.', - $blogs_count - ); - } - - printf( - $message, - admin_url( 'my-sites.php' ), - number_format_i18n( $blogs_count ) - ); - ?> -
- ' . esc_html( $app_name ) . '' ) . ' + ++ +
' . __( 'Be sure to save this in a safe location. You will not be able to retrieve it.' ) . '
'; $args = array( @@ -241,13 +189,98 @@ - + + ++ ' . esc_html( $app_name ) . '' + ); + ?> +
+ + + + + ID, true ); + $blogs_count = count( $blogs ); + + if ( $blogs_count > 1 ) { + ?> ++ the %2$s site in this installation that you have permissions on.', + 'This will grant access to all %2$s sites in this installation that you have permissions on.', + $blogs_count + ); + + if ( is_super_admin() ) { + /* translators: 1: URL to my-sites.php, 2: Number of sites the user has. */ + $message = _n( + 'This will grant access to the %2$s site on the network as you have Super Admin rights.', + 'This will grant access to all %2$s sites on the network as you have Super Admin rights.', + $blogs_count + ); + } + + printf( + $message, + admin_url( 'my-sites.php' ), + number_format_i18n( $blogs_count ) + ); + ?> +
+ + + ++ +
+ + ++ +
+ +
- ' . esc_html(
- add_query_arg(
- array(
- 'site_url' => site_url(),
- 'user_login' => $user->user_login,
- 'password' => '[------]',
- ),
- $success_url
- )
- ) . ''
- );
- } else {
- _e( 'You will be given a password to manually enter into the application in question.' );
- }
- ?>
-
- ' . esc_html( $reject_url ) . ''
- );
- } else {
- _e( 'You will be returned to the WordPress Dashboard, and no changes will be made.' );
- }
- ?>
-