Making WordPress.org


Ignore:
Timestamp:
12/11/2024 05:36:31 AM (18 months ago)
Author:
dd32
Message:

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 #7704.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/plugin-directory/shortcodes/class-release-confirmation.php

    r14245 r14262  
    55use WordPressdotorg\Plugin_Directory\Template;
    66use WordPressdotorg\Plugin_Directory\Tools;
     7use Two_Factor_Core;
     8use function WordPressdotorg\Two_Factor\{
     9    Revalidation\get_status as get_revalidation_status,
     10    Revalidation\get_js_url as get_revalidation_js_url,
     11    get_onboarding_account_url as get_2fa_onboarding_url
     12};
    713
    814/**
     
    1420
    1521    const SHORTCODE = 'release-confirmation';
    16     const COOKIE    = 'release_confirmation_access_token';
    17     const META_KEY  = '_release_confirmation_access_token';
    1822    const URL_PARAM = 'access_token';
    1923
     
    5963        ob_start();
    6064
    61         $should_show_access_notice = false;
    62         foreach ( $plugins as $plugin ) {
    63             if ( $plugin->release_confirmation ) {
    64                 $should_show_access_notice = true;
    65             }
    66         }
    67 
    68         if ( ! self::can_access() && $should_show_access_notice ) {
    69             if ( isset( $_REQUEST['send_access_email'] ) ) {
    70                 printf(
    71                     '<div class="plugin-notice notice notice-info notice-alt"><p>%s</p></div>',
    72                     __( 'Check your email for an access link to perform actions.', 'wporg-plugins')
    73                 );
    74             } else {
    75                 printf(
    76                     '<div class="plugin-notice notice notice-info notice-alt"><p>%s</p></div>',
    77                     sprintf(
    78                         /* translators: %s: URL */
    79                         __( 'Check your email for an access link, or <a href="%s">request a new email</a> to perform actions.', 'wporg-plugins'),
    80                         Template::get_release_confirmation_access_link()
    81                     )
    82                 );
    83             }
     65        // If the user is not using 2FA, show a notice.
     66        if ( ! Two_Factor_Core::is_user_using_two_factor( get_current_user_id() ) ) {
     67            printf(
     68                '<div class="plugin-notice notice notice-error notice-alt"><p>%s</p></div>',
     69                sprintf(
     70                    __( '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' ),
     71                    get_2fa_onboarding_url()
     72                )
     73            );
    8474        }
    8575
     
    246236        $buttons = [];
    247237
    248         if ( $data['confirmations_required'] && empty( $data['discarded'] ) ) {
     238        if (
     239            ! is_user_logged_in() ||
     240            ! Two_Factor_Core::is_user_using_two_factor( get_current_user_id() ) ||
     241            ! current_user_can( 'plugin_manage_releases', $plugin  ) ||
     242
     243            // No need to show actions if the release can't be confirmed, or is already confirmed
     244            ! $data['confirmations_required'] ||
     245            $data['confirmed']
     246        ) {
     247            return '';
     248        }
     249
     250        if ( empty( $data['discarded'] ) ) {
    249251            $current_user_confirmed = isset( $data['confirmations'][ wp_get_current_user()->user_login ] );
    250252
    251             if ( ! $current_user_confirmed && ! $data['confirmed'] ) {
    252                 if (
    253                     self::can_access() &&
    254                     current_user_can( 'plugin_manage_releases', $plugin  )
    255                 ) {
    256                     $buttons[] = sprintf(
    257                         '<a href="%s" class="wp-element-button button approve-release">%s</a>',
    258                         Template::get_release_confirmation_link( $data['tag'], $plugin ),
    259                         __( 'Confirm', 'wporg-plugins' )
    260                     );
    261                     $buttons[] = sprintf(
    262                         '<a href="%s" class="wp-element-button button approve-release">%s</a>',
    263                         Template::get_release_confirmation_link( $data['tag'], $plugin, 'discard' ),
    264                         __( 'Discard', 'wporg-plugins' )
    265                     );
    266                 } else {
    267                     $buttons[] = sprintf(
    268                         '<a class="wp-element-button button approve-release disabled">%s</a>',
    269                         __( 'Confirm', 'wporg-plugins' )
    270                     );
    271                     $buttons[] = sprintf(
    272                         '<a class="wp-element-button button approve-release disabled">%s</a>',
    273                         __( 'Discard', 'wporg-plugins' )
    274                     );
    275                 }
    276 
    277             } elseif ( $current_user_confirmed ) {
     253            if ( ! $current_user_confirmed ) {
     254                $confirm_link = Template::get_release_confirmation_link( $data['tag'], $plugin );
     255                $discard_link = Template::get_release_confirmation_link( $data['tag'], $plugin, 'discard' );
     256
     257                $confirm_link = get_revalidation_js_url( $confirm_link );
     258                $discard_link = get_revalidation_js_url( $discard_link );
     259
    278260                $buttons[] = sprintf(
    279                     '<a class="wp-element-button button approve-release disabled">%s</a>',
    280                     __( 'Confirmed', 'wporg-plugins' )
    281                 );
     261                    '<a href="%s" class="wp-element-button button approve-release" data-2fa-required data-2fa-message="%s">%s</a>',
     262                    $confirm_link,
     263                    esc_attr(
     264                        sprintf(
     265                            /* translators: 1: Version number, 2: Plugin name. */
     266                            __( 'Confirm your Two-Factor Authentication to release version %1$s of %2$s.', 'wporg-plugins' ),
     267                            esc_html( $data['version'] ),
     268                            $plugin->post_title
     269                        )
     270                    ),
     271                    __( 'Confirm', 'wporg-plugins' )
     272                );
     273
     274                $buttons[] = sprintf(
     275                    '<a href="%s" class="wp-element-button button approve-release" data-2fa-required data-2fa-message="%s">%s</a>',
     276                    $discard_link,
     277                    esc_attr(
     278                        sprintf(
     279                            /* translators: 1: Version number, 2: Plugin name. */
     280                            __( 'Confirm your Two-Factor Authentication to discard the release %1$s for %2$s.', 'wporg-plugins' ),
     281                            esc_html( $data['version'] ),
     282                            $plugin->post_title
     283                        )
     284                    ),
     285                    __( 'Discard', 'wporg-plugins' )
     286                );
     287
    282288            }
    283289        } elseif (
     
    297303    }
    298304
    299     static function can_access() {
    300         if ( ! is_user_logged_in() ) {
    301             return false;
    302         }
    303 
    304         // Plugin reviewers can always access the release management functionality, in wp-admin.
    305         if ( current_user_can( 'plugin_review' ) && ( is_admin() || wp_is_serving_rest_request() ) ) {
    306             return true;
    307         }
    308 
    309         // Must have an access token..
    310         if ( empty( $_COOKIE[ self::COOKIE ] ) ) {
    311             return false;
    312         }
    313 
    314         // ...and it be valid..
    315         $token = get_user_meta( get_current_user_id(), self::META_KEY, true );
    316         if (
    317             $token &&
    318             $token['time'] > ( time() - DAY_IN_SECONDS ) &&
    319             wp_check_password( $_COOKIE[ self::COOKIE ], $token['token'] )
    320         ) {
    321             return true;
    322         }
    323 
    324         return false;
    325     }
    326 
    327305    static function generate_access_url( $user = null ) {
    328         if ( ! $user ) {
    329             $user = wp_get_current_user();
    330         }
    331         if ( ! $user || ! $user->exists() ) {
    332             return false;
    333         }
    334 
    335         $time      = time();
    336         $plaintext = wp_generate_password( 24, false );
    337         $token     = wp_hash_password( $plaintext );
    338         update_user_meta( $user->ID, self::META_KEY, compact( 'token', 'time' ) );
    339 
    340         $url = add_query_arg(
    341             self::URL_PARAM,
    342             urlencode( $plaintext ),
    343             home_url( '/developers/releases/' )
    344         );
    345 
    346         return $url;
     306        return home_url( '/developers/releases/' );
    347307    }
    348308
     
    351311        if ( ! $post || ! is_page() || ! has_shortcode( $post->post_content, self::SHORTCODE ) ) {
    352312            return;
    353         }
    354 
    355         // Migrate URL param to cookie.
    356         if ( isset( $_REQUEST[ self::URL_PARAM ] ) ) {
    357             setcookie( self::COOKIE, $_REQUEST[ self::URL_PARAM ], time() + DAY_IN_SECONDS, '/plugins/', 'wordpress.org', true, true );
    358         }
    359 
    360         // Expire the cookie when needed. This is not for security, only performance / cleanliness.
    361         if ( isset( $_COOKIE[ self::COOKIE ] ) && ! self::can_access() ) {
    362             unset( $_COOKIE[ self::COOKIE ] );
    363             setcookie( self::COOKIE, false, time() - DAY_IN_SECONDS );
    364313        }
    365314
Note: See TracChangeset for help on using the changeset viewer.