<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[14262] sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory: Plugin Directory: Require 2FA verification to confirm a plugin release.</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { white-space: pre-line; overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta" style="font-size: 105%">
<dt style="float: left; width: 6em; font-weight: bold">Revision</dt> <dd><a style="font-weight: bold" href="http://meta.trac.wordpress.org/changeset/14262">14262</a><script type="application/ld+json">{"@context":"http://schema.org","@type":"EmailMessage","description":"Review this Commit","action":{"@type":"ViewAction","url":"http://meta.trac.wordpress.org/changeset/14262","name":"Review Commit"}}</script></dd>
<dt style="float: left; width: 6em; font-weight: bold">Author</dt> <dd>dd32</dd>
<dt style="float: left; width: 6em; font-weight: bold">Date</dt> <dd>2024-12-11 05:36:31 +0000 (Wed, 11 Dec 2024)</dd>
</dl>

<pre style='padding-left: 1em; margin: 2em 0; border-left: 2px solid #ccc; line-height: 1.25; font-size: 105%; font-family: sans-serif'>Plugin Directory: Require 2FA verification to confirm a plugin release.

This replaces the email access links.
All plugin committers are required to have 2FA enabled now.

Closes https://github.com/WordPress/wordpress.org/pull/344.
Fixes <a href="http://meta.trac.wordpress.org/ticket/7704">#7704</a>.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryapiroutesclasspluginreleaseconfirmationphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-release-confirmation.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryclasstemplatephp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php</a></li>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryshortcodesclassreleaseconfirmationphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-release-confirmation.php</a></li>
</ul>

<h3>Removed Paths</h3>
<ul>
<li><a href="#sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryemailclassreleaseconfirmationaccessphp">sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/email/class-release-confirmation-access.php</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryapiroutesclasspluginreleaseconfirmationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-release-confirmation.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-release-confirmation.php        2024-12-11 04:26:43 UTC (rev 14261)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/api/routes/class-plugin-release-confirmation.php  2024-12-11 05:36:31 UTC (rev 14262)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -6,9 +6,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use WordPressdotorg\Plugin_Directory\API\Base;
</span><span class="cx" style="display: block; padding: 0 10px"> use WordPressdotorg\Plugin_Directory\Tools;
</span><span class="cx" style="display: block; padding: 0 10px"> use WordPressdotorg\Plugin_Directory\Jobs\Plugin_Import;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WordPressdotorg\Plugin_Directory\Shortcodes\Release_Confirmation as Release_Confirmation_Shortcode;
</del><span class="cx" style="display: block; padding: 0 10px"> use WordPressdotorg\Plugin_Directory\Email\Release_Confirmation_Enabled as Release_Confirmation_Enabled_Email;
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-use WordPressdotorg\Plugin_Directory\Email\Release_Confirmation_Access as Release_Confirmation_Access_Email;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Two_Factor_Core;
+use function WordPressdotorg\Two_Factor\Revalidation\{
+       get_status as get_revalidation_status,
+       get_url as get_revalidation_url,
+};
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * An API endpoint for closing a particular plugin.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -81,14 +84,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        },
</span><span class="cx" style="display: block; padding: 0 10px">                ] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                register_rest_route( 'plugins/v1', '/release-confirmation-access', [
-                       'methods'             => \WP_REST_Server::READABLE,
-                       'callback'            => [ $this, 'send_access_email' ],
-                       'args'                => [
-                       ],
-                       'permission_callback' => 'is_user_logged_in',
-               ] );
-
</del><span class="cx" style="display: block; padding: 0 10px">                 add_filter( 'rest_pre_echo_response', [ $this, 'override_cookie_expired_message' ], 10, 3 );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -118,10 +113,31 @@
</span><span class="cx" style="display: block; padding: 0 10px">        public function permission_can_access_plugin( $request ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $plugin = Plugin_Directory::get_plugin_post( $request['plugin_slug'] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                return (
-                       Release_Confirmation_Shortcode::can_access() &&
-                       current_user_can( 'plugin_manage_releases', $plugin )
-               );
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if ( ! $plugin || ! current_user_can( 'plugin_manage_releases', $plugin ) ) {
+                       return false;
+               }
+
+               // Check to see if they've confirmed their 2FA status recently..
+               $status = get_revalidation_status();
+               if ( $status && $status['can_save'] ) {
+                       return true;
+               }
+
+               // Before we say no, check if the user just needs to validate their 2FA.
+               if ( $status && $status['needs_revalidate'] && 'GET' === $request->get_method() ) {
+                       $current_rest_url = add_query_arg(
+                               array(
+                                       '_wpnonce'         => wp_create_nonce( 'wp_rest' ),
+                                       '_wp_http_referer' => wp_get_referer(),
+                               ),
+                               get_rest_url( null, $request->get_route() )
+                       );
+
+                       wp_safe_redirect( get_revalidation_url( $current_rest_url ) );
+                       exit;
+               }
+
+               return false;
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -299,24 +315,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return $result;
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        /**
-        * Send a Access email
-        */
-       public function send_access_email( $request ) {
-               $result = [
-                       'location' => wp_get_referer() ?: home_url( '/developers/releases/' ),
-               ];
-               $result['location'] = add_query_arg( 'send_access_email', '1', $result['location'] );
-               header( 'Location: ' . $result['location'] );
-
-               $email = new Release_Confirmation_Access_Email(
-                       wp_get_current_user()
-               );
-               $result['sent'] = $email->send();
-
-               return $result;
-       }
-
</del><span class="cx" style="display: block; padding: 0 10px">         public function validate_plugin_tag_callback( $tag, $request ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $plugin = Plugin_Directory::get_plugin_post( $request['plugin_slug'] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryclasstemplatephp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php      2024-12-11 04:26:43 UTC (rev 14261)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/class-template.php        2024-12-11 05:36:31 UTC (rev 14262)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1002,19 +1002,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-         * Generates a link to email the release confirmation link.
-        *
-        * @param int|\WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
-        * @return string URL to enable confirmations.
-        */
-       public static function get_release_confirmation_access_link() {
-               return add_query_arg(
-                       array( '_wpnonce' => wp_create_nonce( 'wp_rest' ) ),
-                       home_url( 'wp-json/plugins/v1/release-confirmation-access' )
-               );
-       }
-
-       /**
</del><span class="cx" style="display: block; padding: 0 10px">          * Returns the reasons for closing or disabling a plugin.
</span><span class="cx" style="display: block; padding: 0 10px">         *
</span><span class="cx" style="display: block; padding: 0 10px">         * @return array Close/disable reason labels.
</span></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryemailclassreleaseconfirmationaccessphp"></a>
<div class="delfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Deleted: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/email/class-release-confirmation-access.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/email/class-release-confirmation-access.php     2024-12-11 04:26:43 UTC (rev 14261)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/email/class-release-confirmation-access.php       2024-12-11 05:36:31 UTC (rev 14262)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -1,29 +0,0 @@
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-<?php
-namespace WordPressdotorg\Plugin_Directory\Email;
-
-use WordPressdotorg\Plugin_Directory\Tools;
-use WordPressdotorg\Plugin_Directory\Shortcodes\Release_Confirmation as Release_Confirmation_Shortcode;
-
-class Release_Confirmation_Access extends Base {
-       protected $required_args = [];
-
-       function subject() {
-               return sprintf(
-                       /* translators: 1: User Login */
-                       __( 'Release Management for %s', 'wporg-plugins' ),
-                       $this->user->user_login
-               );
-       }
-
-       function body() {
-               return sprintf(
-                       /* translators: 1: Username; 1: Access URL */
-                       __( 'Howdy %1$s,
-
-To manage your plugin releases, follow the link below:
-%2$s', 'wporg-plugins' ),
-                       $this->user_text( $this->user ),
-                       esc_url( Release_Confirmation_Shortcode::generate_access_url( $this->user ) )
-               );
-       }
-}
</del></span></pre></div>
<a id="sitestrunkwordpressorgpublic_htmlwpcontentpluginsplugindirectoryshortcodesclassreleaseconfirmationphp"></a>
<div class="modfile"><h4 style="background-color: #eee; color: inherit; margin: 1em 0; padding: 1.3em; font-size: 115%">Modified: sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-release-confirmation.php</h4>
<pre class="diff"><span>
<span class="info" style="display: block; padding: 0 10px; color: #888">--- sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-release-confirmation.php       2024-12-11 04:26:43 UTC (rev 14261)
+++ sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-release-confirmation.php 2024-12-11 05:36:31 UTC (rev 14262)
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -4,6 +4,12 @@
</span><span class="cx" style="display: block; padding: 0 10px"> use WordPressdotorg\Plugin_Directory\Plugin_Directory;
</span><span class="cx" style="display: block; padding: 0 10px"> use WordPressdotorg\Plugin_Directory\Template;
</span><span class="cx" style="display: block; padding: 0 10px"> use WordPressdotorg\Plugin_Directory\Tools;
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+use Two_Factor_Core;
+use function WordPressdotorg\Two_Factor\{
+       Revalidation\get_status as get_revalidation_status,
+       Revalidation\get_js_url as get_revalidation_js_url,
+       get_onboarding_account_url as get_2fa_onboarding_url
+};
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px"> /**
</span><span class="cx" style="display: block; padding: 0 10px">  * The [release-confirmation] shortcode handler.
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -13,8 +19,6 @@
</span><span class="cx" style="display: block; padding: 0 10px"> class Release_Confirmation {
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        const SHORTCODE = 'release-confirmation';
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        const COOKIE    = 'release_confirmation_access_token';
-       const META_KEY  = '_release_confirmation_access_token';
</del><span class="cx" style="display: block; padding: 0 10px">         const URL_PARAM = 'access_token';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        /**
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -58,31 +62,17 @@
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                ob_start();
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                $should_show_access_notice = false;
-               foreach ( $plugins as $plugin ) {
-                       if ( $plugin->release_confirmation ) {
-                               $should_show_access_notice = true;
-                       }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         // If the user is not using 2FA, show a notice.
+               if ( ! Two_Factor_Core::is_user_using_two_factor( get_current_user_id() ) ) {
+                       printf(
+                               '<div class="plugin-notice notice notice-error notice-alt"><p>%s</p></div>',
+                               sprintf(
+                                       __( 'Your account has elevated privileges and requires extra security before you can manage plugin releases. Please <a href="%s">enable two-factor authentication now</a>.', 'wporg-plugins' ),
+                                       get_2fa_onboarding_url()
+                               )
+                       );
</ins><span class="cx" style="display: block; padding: 0 10px">                 }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! self::can_access() && $should_show_access_notice ) {
-                       if ( isset( $_REQUEST['send_access_email'] ) ) {
-                               printf(
-                                       '<div class="plugin-notice notice notice-info notice-alt"><p>%s</p></div>',
-                                       __( 'Check your email for an access link to perform actions.', 'wporg-plugins')
-                               );
-                       } else {
-                               printf(
-                                       '<div class="plugin-notice notice notice-info notice-alt"><p>%s</p></div>',
-                                       sprintf(
-                                               /* translators: %s: URL */
-                                               __( 'Check your email for an access link, or <a href="%s">request a new email</a> to perform actions.', 'wporg-plugins'),
-                                               Template::get_release_confirmation_access_link()
-                                       )
-                               );
-                       }
-               }
-
</del><span class="cx" style="display: block; padding: 0 10px">                 echo '<p>' . __( 'This page is for authorized committers to view and manage releases of their plugins. Plugins with confirmations enabled require an extra action on this page to approve each new release.', 'wporg-plugins' ) . '</p>';
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">                /* translators: %s: plugins@wordpress.org */
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -245,40 +235,56 @@
</span><span class="cx" style="display: block; padding: 0 10px">        static function get_actions( $plugin, $data ) {
</span><span class="cx" style="display: block; padding: 0 10px">                $buttons = [];
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( $data['confirmations_required'] && empty( $data['discarded'] ) ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         if (
+                       ! is_user_logged_in() ||
+                       ! Two_Factor_Core::is_user_using_two_factor( get_current_user_id() ) ||
+                       ! current_user_can( 'plugin_manage_releases', $plugin  ) ||
+
+                       // No need to show actions if the release can't be confirmed, or is already confirmed
+                       ! $data['confirmations_required'] ||
+                       $data['confirmed']
+               ) {
+                       return '';
+               }
+
+               if ( empty( $data['discarded'] ) ) {
</ins><span class="cx" style="display: block; padding: 0 10px">                         $current_user_confirmed = isset( $data['confirmations'][ wp_get_current_user()->user_login ] );
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        if ( ! $current_user_confirmed && ! $data['confirmed'] ) {
-                               if (
-                                       self::can_access() &&
-                                       current_user_can( 'plugin_manage_releases', $plugin  )
-                               ) {
-                                       $buttons[] = sprintf(
-                                               '<a href="%s" class="wp-element-button button approve-release">%s</a>',
-                                               Template::get_release_confirmation_link( $data['tag'], $plugin ),
-                                               __( 'Confirm', 'wporg-plugins' )
-                                       );
-                                       $buttons[] = sprintf(
-                                               '<a href="%s" class="wp-element-button button approve-release">%s</a>',
-                                               Template::get_release_confirmation_link( $data['tag'], $plugin, 'discard' ),
-                                               __( 'Discard', 'wporg-plugins' )
-                                       );
-                               } else {
-                                       $buttons[] = sprintf(
-                                               '<a class="wp-element-button button approve-release disabled">%s</a>',
-                                               __( 'Confirm', 'wporg-plugins' )
-                                       );
-                                       $buttons[] = sprintf(
-                                               '<a class="wp-element-button button approve-release disabled">%s</a>',
-                                               __( 'Discard', 'wporg-plugins' )
-                                       );
-                               }
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                 if ( ! $current_user_confirmed ) {
+                               $confirm_link = Template::get_release_confirmation_link( $data['tag'], $plugin );
+                               $discard_link = Template::get_release_confirmation_link( $data['tag'], $plugin, 'discard' );
</ins><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                        } elseif ( $current_user_confirmed ) {
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                         $confirm_link = get_revalidation_js_url( $confirm_link );
+                               $discard_link = get_revalidation_js_url( $discard_link );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                                 $buttons[] = sprintf(
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                                        '<a class="wp-element-button button approve-release disabled">%s</a>',
-                                       __( 'Confirmed', 'wporg-plugins' )
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+                                 '<a href="%s" class="wp-element-button button approve-release" data-2fa-required data-2fa-message="%s">%s</a>',
+                                       $confirm_link,
+                                       esc_attr(
+                                               sprintf(
+                                                       /* translators: 1: Version number, 2: Plugin name. */
+                                                       __( 'Confirm your Two-Factor Authentication to release version %1$s of %2$s.', 'wporg-plugins' ),
+                                                       esc_html( $data['version'] ),
+                                                       $plugin->post_title
+                                               )
+                                       ),
+                                       __( 'Confirm', 'wporg-plugins' )
</ins><span class="cx" style="display: block; padding: 0 10px">                                 );
</span><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+
+                               $buttons[] = sprintf(
+                                       '<a href="%s" class="wp-element-button button approve-release" data-2fa-required data-2fa-message="%s">%s</a>',
+                                       $discard_link,
+                                       esc_attr(
+                                               sprintf(
+                                                       /* translators: 1: Version number, 2: Plugin name. */
+                                                       __( 'Confirm your Two-Factor Authentication to discard the release %1$s for %2$s.', 'wporg-plugins' ),
+                                                       esc_html( $data['version'] ),
+                                                       $plugin->post_title
+                                               )
+                                       ),
+                                       __( 'Discard', 'wporg-plugins' )
+                               );
+
</ins><span class="cx" style="display: block; padding: 0 10px">                         }
</span><span class="cx" style="display: block; padding: 0 10px">                } elseif (
</span><span class="cx" style="display: block; padding: 0 10px">                        $data['discarded'] &&
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -296,54 +302,8 @@
</span><span class="cx" style="display: block; padding: 0 10px">                return implode( ' ', $buttons );
</span><span class="cx" style="display: block; padding: 0 10px">        }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-        static function can_access() {
-               if ( ! is_user_logged_in() ) {
-                       return false;
-               }
-
-               // Plugin reviewers can always access the release management functionality, in wp-admin.
-               if ( current_user_can( 'plugin_review' ) && ( is_admin() || wp_is_serving_rest_request() ) ) {
-                       return true;
-               }
-
-               // Must have an access token..
-               if ( empty( $_COOKIE[ self::COOKIE ] ) ) {
-                       return false;
-               }
-
-               // ...and it be valid..
-               $token = get_user_meta( get_current_user_id(), self::META_KEY, true );
-               if (
-                       $token &&
-                       $token['time'] > ( time() - DAY_IN_SECONDS ) &&
-                       wp_check_password( $_COOKIE[ self::COOKIE ], $token['token'] )
-               ) {
-                       return true;
-               }
-
-               return false;
-       }
-
</del><span class="cx" style="display: block; padding: 0 10px">         static function generate_access_url( $user = null ) {
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                if ( ! $user ) {
-                       $user = wp_get_current_user();
-               }
-               if ( ! $user || ! $user->exists() ) {
-                       return false;
-               }
-
-               $time      = time();
-               $plaintext = wp_generate_password( 24, false );
-               $token     = wp_hash_password( $plaintext );
-               update_user_meta( $user->ID, self::META_KEY, compact( 'token', 'time' ) );
-
-               $url = add_query_arg(
-                       self::URL_PARAM,
-                       urlencode( $plaintext ),
-                       home_url( '/developers/releases/' )
-               );
-
-               return $url;
</del><ins style="background-color: #dfd; text-decoration:none; display:block; padding: 0 10px">+         return home_url( '/developers/releases/' );
</ins><span class="cx" style="display: block; padding: 0 10px">         }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><span class="cx" style="display: block; padding: 0 10px">        static function template_redirect() {
</span><span class="lines" style="display: block; padding: 0 10px; color: #888">@@ -352,17 +312,6 @@
</span><span class="cx" style="display: block; padding: 0 10px">                        return;
</span><span class="cx" style="display: block; padding: 0 10px">                }
</span><span class="cx" style="display: block; padding: 0 10px"> 
</span><del style="background-color: #fdd; text-decoration:none; display:block; padding: 0 10px">-                // Migrate URL param to cookie.
-               if ( isset( $_REQUEST[ self::URL_PARAM ] ) ) {
-                       setcookie( self::COOKIE, $_REQUEST[ self::URL_PARAM ], time() + DAY_IN_SECONDS, '/plugins/', 'wordpress.org', true, true );
-               }
-
-               // Expire the cookie when needed. This is not for security, only performance / cleanliness.
-               if ( isset( $_COOKIE[ self::COOKIE ] ) && ! self::can_access() ) {
-                       unset( $_COOKIE[ self::COOKIE ] );
-                       setcookie( self::COOKIE, false, time() - DAY_IN_SECONDS );
-               }
-
</del><span class="cx" style="display: block; padding: 0 10px">                 // This page requires login.
</span><span class="cx" style="display: block; padding: 0 10px">                if ( ! is_user_logged_in() ) {
</span><span class="cx" style="display: block; padding: 0 10px">                        wp_safe_redirect( wp_login_url( get_permalink() ) );
</span></span></pre>
</div>
</div>

</body>
</html>