diff --git a/src/js/_enqueues/admin/auth-app.js b/src/js/_enqueues/admin/auth-app.js index 99478d1824a2d..2de15c1d55a95 100644 --- a/src/js/_enqueues/admin/auth-app.js +++ b/src/js/_enqueues/admin/auth-app.js @@ -12,6 +12,7 @@ context = { userLogin: authApp.user_login, successUrl: authApp.success, + successFormat: authApp.success_format, rejectUrl: authApp.reject }; @@ -48,8 +49,9 @@ * @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. + * @param {string} context.successUrl The URL the user will be redirected to after approving the request. + * @param {string} context.successFormat The transport used for the success payload. + * @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 ); @@ -75,15 +77,27 @@ wp.hooks.doAction( 'wp_application_passwords_approve_app_request_success', response, textStatus, jqXHR ); var raw = authApp.success, - url, message, $notice; + url, message, $notice, $callbackForm; if ( raw ) { - url = raw + ( -1 === raw.indexOf( '?' ) ? '?' : '&' ) + - 'site_url=' + encodeURIComponent( authApp.site_url ) + - '&user_login=' + encodeURIComponent( authApp.user_login ) + - '&password=' + encodeURIComponent( response.password ); - - window.location = url; + if ( 'form_post' === authApp.success_format ) { + $callbackForm = $( '
' ) + .attr( 'method', 'post' ) + .attr( 'action', raw ) + .hide() + .append( $( '' ).attr( 'type', 'hidden' ).attr( 'name', 'site_url' ).val( authApp.site_url ) ) + .append( $( '' ).attr( 'type', 'hidden' ).attr( 'name', 'user_login' ).val( authApp.user_login ) ) + .append( $( '' ).attr( 'type', 'hidden' ).attr( 'name', 'password' ).val( response.password ) ); + $( document.body ).append( $callbackForm ); + $callbackForm[0].submit(); + } else { + url = raw + ( -1 === raw.indexOf( '?' ) ? '?' : '&' ) + + 'site_url=' + encodeURIComponent( authApp.site_url ) + + '&user_login=' + encodeURIComponent( authApp.user_login ) + + '&password=' + encodeURIComponent( response.password ); + + window.location = url; + } } else { message = wp.i18n.sprintf( /* translators: %s: Application name. */ @@ -150,8 +164,9 @@ * * @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. + * @param {string} context.successUrl The URL the user will be redirected to after approving the request. + * @param {string} context.successFormat The transport used for the success payload. + * @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 ); diff --git a/src/wp-admin/authorize-application.php b/src/wp-admin/authorize-application.php index 8d931f46666a2..15487696de978 100644 --- a/src/wp-admin/authorize-application.php +++ b/src/wp-admin/authorize-application.php @@ -11,16 +11,27 @@ $error = null; $new_password = ''; +$user = wp_get_current_user(); // This is the no-js fallback script. Generally this will all be handled by `auth-app.js`. if ( isset( $_POST['action'] ) && 'authorize_application_password' === $_POST['action'] ) { 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 = ''; + $success_url = $_POST['success_url']; + $reject_url = $_POST['reject_url']; + $app_name = $_POST['app_name']; + $app_id = $_POST['app_id']; + $success_format = isset( $_POST['success_format'] ) ? $_POST['success_format'] : 'query'; + $redirect = ''; + $request = compact( 'app_name', 'app_id', 'success_url', 'reject_url', 'success_format' ); + $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 ( isset( $_POST['reject'] ) ) { if ( $reject_url ) { @@ -43,14 +54,42 @@ list( $new_password ) = $created; if ( $success_url ) { - $redirect = add_query_arg( - array( - 'site_url' => urlencode( site_url() ), - 'user_login' => urlencode( wp_get_current_user()->user_login ), - 'password' => urlencode( $new_password ), - ), - $success_url - ); + if ( 'form_post' === $success_format ) { + ?> + + > + + + + <?php esc_html_e( 'Authorizing Application' ); ?> + + + + + + + +
+ + + + site_url(), + 'user_login' => $user->user_login, + 'password' => $new_password, + ), + $success_url + ); + } } } } @@ -65,9 +104,10 @@ // Used in the HTML title tag. $title = __( 'Authorize Application' ); -$app_name = ! empty( $_REQUEST['app_name'] ) ? $_REQUEST['app_name'] : ''; -$app_id = ! empty( $_REQUEST['app_id'] ) ? $_REQUEST['app_id'] : ''; -$success_url = ! empty( $_REQUEST['success_url'] ) ? $_REQUEST['success_url'] : null; +$app_name = ! empty( $_REQUEST['app_name'] ) ? $_REQUEST['app_name'] : ''; +$app_id = ! empty( $_REQUEST['app_id'] ) ? $_REQUEST['app_id'] : ''; +$success_url = ! empty( $_REQUEST['success_url'] ) ? $_REQUEST['success_url'] : null; +$success_format = ! empty( $_REQUEST['success_format'] ) ? $_REQUEST['success_format'] : 'query'; if ( ! empty( $_REQUEST['reject_url'] ) ) { $reject_url = $_REQUEST['reject_url']; @@ -77,9 +117,7 @@ $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', 'reject_url', 'success_format' ); $is_valid = wp_is_authorize_application_password_request_valid( $request, $user ); if ( is_wp_error( $is_valid ) ) { @@ -124,10 +162,11 @@ '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, + 'success_format' => $success_format, + 'reject' => $reject_url ? $reject_url : admin_url(), ) ); @@ -241,6 +280,7 @@ +
@@ -280,20 +320,28 @@

' . esc_html( - add_query_arg( - array( - 'site_url' => site_url(), - 'user_login' => $user->user_login, - 'password' => '[------]', - ), - $success_url - ) - ) . '' - ); + if ( 'form_post' === $success_format ) { + printf( + /* translators: %s: The URL the user is being redirected to. */ + __( 'Your connection details will be sent to %s.' ), + '' . esc_html( $success_url ) . '' + ); + } else { + printf( + /* translators: %s: The URL the user is being redirected to. */ + __( 'You will be sent to %s' ), + '' . 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.' ); } diff --git a/src/wp-admin/includes/user.php b/src/wp-admin/includes/user.php index 477ee9b5af4b7..d042314e8abad 100644 --- a/src/wp-admin/includes/user.php +++ b/src/wp-admin/includes/user.php @@ -643,10 +643,11 @@ function admin_created_user_email( $text ) { * @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 $app_id A UUID provided by the application to uniquely identify it. - * @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. + * @type string $app_name The suggested name of the application. + * @type string $app_id A UUID provided by the application to uniquely identify it. + * @type string $success_url The URL the user will be redirected to after approving the application. + * @type string $success_format The transport to use for the success payload. Accepts 'query' or 'form_post'. + * @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. @@ -674,6 +675,27 @@ function wp_is_authorize_application_password_request_valid( $request, $user ) { } } + if ( isset( $request['success_format'] ) && ! in_array( $request['success_format'], array( 'query', 'form_post' ), true ) ) { + $error->add( + 'invalid_success_format', + __( 'The success format must be "query" or "form_post".' ) + ); + } + + $success_url_scheme = ! empty( $request['success_url'] ) ? wp_parse_url( $request['success_url'], PHP_URL_SCHEME ) : ''; + + if ( + ! empty( $request['success_url'] ) && + isset( $request['success_format'] ) && + 'form_post' === $request['success_format'] && + ! in_array( strtolower( (string) $success_url_scheme ), array( 'http', 'https' ), true ) + ) { + $error->add( + 'invalid_success_format', + __( 'The form_post success format requires an HTTP or HTTPS success URL.' ) + ); + } + if ( ! empty( $request['app_id'] ) && ! wp_is_uuid( $request['app_id'] ) ) { $error->add( 'invalid_app_id', diff --git a/tests/phpunit/tests/admin/includes/user/WpIsAuthorizeApplicationPasswordRequestValid_Test.php b/tests/phpunit/tests/admin/includes/user/WpIsAuthorizeApplicationPasswordRequestValid_Test.php index a2e1320b2a150..866629a0ef97e 100644 --- a/tests/phpunit/tests/admin/includes/user/WpIsAuthorizeApplicationPasswordRequestValid_Test.php +++ b/tests/phpunit/tests/admin/includes/user/WpIsAuthorizeApplicationPasswordRequestValid_Test.php @@ -93,6 +93,36 @@ public function data_is_authorize_application_password_request_valid() { 'expected_error_code' => '', 'env' => $environment_type, ); + + $datasets[ $environment_type . ' and a "query" success_format' ] = array( + 'request' => array( 'success_format' => 'query' ), + 'expected_error_code' => '', + 'env' => $environment_type, + ); + + $datasets[ $environment_type . ' and a "form_post" success_format with an "https" scheme "success_url"' ] = array( + 'request' => array( + 'success_url' => 'https://example.org', + 'success_format' => 'form_post', + ), + 'expected_error_code' => '', + 'env' => $environment_type, + ); + + $datasets[ $environment_type . ' and an invalid success_format' ] = array( + 'request' => array( 'success_format' => 'invalid' ), + 'expected_error_code' => 'invalid_success_format', + 'env' => $environment_type, + ); + + $datasets[ $environment_type . ' and a "form_post" success_format with an app scheme "success_url"' ] = array( + 'request' => array( + 'success_url' => 'wordpress://example', + 'success_format' => 'form_post', + ), + 'expected_error_code' => 'invalid_success_format', + 'env' => $environment_type, + ); } return $datasets;