WordPress.org

Making WordPress.org

Changeset 6781


Ignore:
Timestamp:
02/27/2018 02:30:31 AM (3 years ago)
Author:
dd32
Message:

2FA: Switch from using a nonced login to defining a wporg_2fa cookie instead.

This works by making the WordPress cookies only valid when the 2FA cookie is also set.
This differs from the two-factoe plugin which only sets auth cookies after 2FA is completed.

See #77.

File:
1 edited

Legend:

Unmodified
Added
Removed
  • sites/trunk/wordpress.org/public_html/wp-content/plugins/wporg-two-factor/wporg-two-factor.php

    r6775 r6781  
    1313class WPORG_Two_Factor extends Two_Factor_Core {
    1414
     15    const WPORG_2FA_COOKIE = 'wporg_2fa';
     16
    1517    public function __construct() {
    1618        add_filter( 'two_factor_providers', [ $this, 'two_factor_providers' ] );
     19
     20        add_filter( 'determine_current_user', [ $this, 'disable_authentication_without_2fa' ], 20 ); // Cookies at priority 10, Must be > 11
     21        add_action( 'clear_auth_cookie',      [ $this, 'clear_2fa_cookies' ] );
     22        add_filter( 'salt',                   [ $this, 'add_2fa_salt' ], 10, 2 );
    1723
    1824        remove_action( 'edit_user_profile', [ 'Two_Factor_Core', 'user_two_factor_options' ] );
     
    3642        add_action( 'login_form_backup_2fa',   [ $this, 'backup_2fa' ] );
    3743
     44    }
     45
     46    function add_2fa_salt( $salt, $scheme ) {
     47        if ( '2fa' == $scheme ) {
     48            $salt = defined( 'WPORG_2FA_KEY' ) ? WPORG_2FA_KEY : AUTH_KEY;
     49        }
     50
     51        return $salt;
     52    }
     53
     54    function disable_authentication_without_2fa( $user_id ) {
     55        if ( ! $user_id ) {
     56            return $user_id;
     57        }
     58        // User is logged in:
     59
     60        // If the user isn't a 2FA user, allow.
     61        if ( ! self::is_user_using_two_factor( $user_id ) ) {
     62            return $user_id;
     63        }
     64
     65        // If the user has a valid 2FA cookie, allow
     66        if ( isset( $_COOKIE[ self::WPORG_2FA_COOKIE ] ) && wp_validate_auth_cookie( $_COOKIE[ self::WPORG_2FA_COOKIE ], '2fa' ) ) {
     67            return $user_id;
     68        }
     69
     70        // If the user did not authenticate via Cookie, allow
     71        if ( ! wp_validate_auth_cookie( false ) && ! wp_validate_logged_in_cookie( false ) ) {
     72            // The user wasn't authenticated by cookie, so allow the auth.
     73            return $user_id;
     74        }
     75
     76        // If they're on the 2FA login page, allow
     77        $login_host = class_exists( 'WPOrg_SSO' ) ? WPOrg_SSO::SSO_HOST : 'login.wordpress.org';
     78        if ( $login_host === $_SERVER['HTTP_HOST']  ) {
     79            if ( '/wp-login.php' == substr( $_SERVER['REQUEST_URI'], 0, 13 ) ) {
     80                if ( $_POST || 'backup_2fa' == $_REQUEST['action'] || 'validate_2fa' == $_REQUEST['action'] ) {
     81                    return $user_id;
     82                }
     83            }
     84        }
     85
     86        /*
     87         * Fail. We've checked that:
     88         * - the user has 2FA enabled
     89         * - doesn't have a valid 2FA cookie
     90         * - the user is logged in via cookie
     91         * - isn't currently logging in on the SSO host
     92         *
     93         * The users cookies are not valid until that 2FA cookie is set.
     94         */
     95        return 0;
     96    }
     97
     98    function clear_2fa_cookies() {
     99        setcookie( self::WPORG_2FA_COOKIE, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH,   COOKIE_DOMAIN );
     100        setcookie( self::WPORG_2FA_COOKIE, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN );
     101    }
     102
     103    function set_2fa_cookies( $user ) {
     104        // Set the Expiration based on the main Authentication cookie
     105        $auth_cookie_parts = wp_parse_auth_cookie( '', 'secure_auth' );
     106        if ( ! $auth_cookie_parts  ) {
     107            wp_logout();
     108            return;
     109        }
     110
     111        $expiration = $auth_cookie_parts['expiration'];
     112
     113        $cookie_value = wp_generate_auth_cookie( $user->ID, $expiration, '2fa', '' /* WordPress.org doesn't use Session Tokens yet */ );
     114
     115        setcookie( self::WPORG_2FA_COOKIE, $cookie_value, $expiration, ADMIN_COOKIE_PATH,   COOKIE_DOMAIN, true, true );
     116        setcookie( self::WPORG_2FA_COOKIE, $cookie_value, $expiration, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, true, true );
    38117    }
    39118
     
    51130        }
    52131
    53         wp_clear_auth_cookie();
    54 
    55132        wp_enqueue_style( 'two-factor-login', plugins_url( '/css/login.css', __FILE__ ) );
    56133
    57         self::show_two_factor_login( $user );
     134        $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : $_SERVER['REQUEST_URI'];
     135        self::login_html( $user, '', $redirect_to );
     136
    58137        exit;
    59138    }
     
    65144     */
    66145    public static function login_form_validate_2fa() {
    67         if ( ! isset( $_POST['wp-auth-id'], $_POST['wp-auth-nonce'] ) ) {
     146        if ( ! is_user_logged_in() ) {
    68147            return;
    69148        }
    70149
    71         $user = get_userdata( $_POST['wp-auth-id'] );
    72         if ( ! $user ) {
    73             return;
    74         }
    75 
    76         $nonce = $_POST['wp-auth-nonce'];
    77         if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
    78             wp_safe_redirect( get_bloginfo( 'url' ) );
    79             exit;
    80         }
     150        $user = wp_get_current_user();
    81151
    82152        if ( isset( $_POST['provider'] ) ) {
     
    85155                $provider = $providers[ $_POST['provider'] ];
    86156            } else {
    87                 wp_die( esc_html__( 'Cheatin’ uh?' ), 403 );
     157                wp_die( 'A valid 2FA provider could not be found.', 403 );
    88158            }
    89159        } else {
     
    93163        // Allow the provider to re-send codes, etc.
    94164        if ( true === $provider->pre_process_authentication( $user ) ) {
    95             $login_nonce = self::create_login_nonce( $user->ID );
    96             if ( ! $login_nonce ) {
    97                 wp_die( esc_html__( 'Failed to create a login nonce.', 'two-factor' ) );
    98             }
    99 
    100             self::login_html( $user, $login_nonce['key'], $_REQUEST['redirect_to'], '', $provider );
     165            self::login_html( $user, '', $_REQUEST['redirect_to'], '', $provider );
    101166            exit;
    102167        }
     
    106171            do_action( 'wp_login_failed', $user->user_login );
    107172
    108             $login_nonce = self::create_login_nonce( $user->ID );
    109             if ( ! $login_nonce ) {
    110                 wp_die( esc_html__( 'Failed to create a login nonce.', 'two-factor' ) );
    111             }
    112 
    113             self::login_html( $user, $login_nonce['key'], $_REQUEST['redirect_to'], esc_html__( 'ERROR: Invalid verification code.', 'two-factor' ), $provider );
     173            self::login_html( $user, '', $_REQUEST['redirect_to'], esc_html__( 'ERROR: Invalid verification code.', 'wporg' ), $provider );
    114174            exit;
    115175        }
    116176
    117         self::delete_login_nonce( $user->ID );
    118 
    119         $rememberme = false;
    120         if ( isset( $_REQUEST['rememberme'] ) && $_REQUEST['rememberme'] ) {
    121             $rememberme = true;
    122         }
    123 
    124         wp_set_auth_cookie( $user->ID, $rememberme );
     177        $this->set_2fa_cookies( $user );
    125178
    126179        // Must be global because that's how login_header() uses it.
     
    160213     */
    161214    public static function backup_2fa() {
    162         if ( ! isset( $_GET['wp-auth-id'], $_GET['wp-auth-nonce'], $_GET['provider'] ) ) {
     215        if ( ! is_user_logged_in() ) {
    163216            return;
    164217        }
    165218
    166         $user = get_userdata( $_GET['wp-auth-id'] );
    167         if ( ! $user ) {
    168             return;
    169         }
    170 
    171         $nonce = $_GET['wp-auth-nonce'];
    172         if ( true !== self::verify_login_nonce( $user->ID, $nonce ) ) {
    173             wp_safe_redirect( get_bloginfo( 'url' ) );
    174             exit;
    175         }
     219        $user = wp_get_current_user();
    176220
    177221        $providers = self::get_available_providers_for_user( $user );
     
    179223            $provider = $providers[ $_GET['provider'] ];
    180224        } else {
    181             wp_die( esc_html__( 'Cheatin’ uh?' ), 403 );
    182         }
    183 
    184         self::login_html( $user, $_GET['wp-auth-nonce'], $_GET['redirect_to'], '', $provider );
     225            wp_die( 'No 2FA provider could be found.', 403 );
     226        }
     227
     228        self::login_html( $user, '', $_GET['redirect_to'], '', $provider );
    185229
    186230        exit;
    187231    }
    188232
    189     public function two_factor_providers( $providers) {
    190         $wporg_providers = array(
     233    public function two_factor_providers() {
     234        return array(
    191235            'WPORG_Two_Factor_Primary'   => __DIR__ . '/providers/class-wporg-two-factor-primary.php',
    192236            'WPORG_Two_Factor_Secondary' => __DIR__ . '/providers/class-wporg-two-factor-secondary.php',
    193237        );
    194 
    195         return $wporg_providers;
    196238    }
    197239
     
    201243     */
    202244    public static function enable_two_factor( $user_id ) {
    203         return (
     245        $result = (
    204246            update_user_meta( $user_id, self::PROVIDER_USER_META_KEY,          'WPORG_Two_Factor_Primary' ) &&
    205247            update_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, [ 'WPORG_Two_Factor_Primary', 'WPORG_Two_Factor_Secondary' ] )
    206248        );
     249
     250        if ( $result && $user_id == get_current_user_id() ) {
     251            $user = wp_get_current_user();
     252            $this->set_2fa_cookies( $user );
     253        }
     254
     255        return $result;
    207256    }
    208257
Note: See TracChangeset for help on using the changeset viewer.