is_two_screen_signin_enabled() ) { add_action( 'login_form', array( $this, 'loginform' ) ); add_action( 'login_footer', array( $this, 'loginfooter' ) ); } add_filter( 'authenticate', array( $this, 'check_otp' ), 50, 3 ); if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { add_action( 'wp_ajax_GoogleAuthenticator_action', array( $this, 'ajax_callback' ) ); } add_action( 'personal_options_update', array( $this, 'personal_options_update' ) ); add_action( 'profile_personal_options', array( $this, 'profile_personal_options' ) ); add_action( 'edit_user_profile', array( $this, 'edit_user_profile' ) ); add_action( 'edit_user_profile_update', array( $this, 'edit_user_profile_update' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'add_qrcode_script' ) ); add_action( 'admin_menu', array ( $this, 'add_pages' ) ); add_action( 'network_admin_menu', array ( $this, 'add_pages' ) ); add_action( 'current_screen', array ( $this, 'redirect_if_setup_required' ) ); add_action( 'admin_notices', array ( $this, 'successful_signup_message' ) ); add_action( 'load-admin_page_google_authenticator_user_page', array( $this, 'save_submitted_setup_page' ) ); load_plugin_textdomain( 'google-authenticator', false, basename( dirname( __FILE__ ) ) . '/lang' ); } /** * Whether we show Google Auth code on the login screen, or after the user has entered their username and password. * * If it's on a separate screen, it means username / passwords can still be bruteforced, but logins can't occur without 2fa * * @return bool */ function is_two_screen_signin_enabled() { $two_screen_mfa = is_multisite() ? get_site_option( 'googleauthenticator_two_screen_signin' ) : get_option( 'googleauthenticator_two_screen_signin' ); return !! $two_screen_mfa; } /** * Check the verification code entered by the user. */ function verify( $secretkey, $thistry, $relaxedmode, $lasttimeslot ) { // Did the user enter 6 digits ? if ( strlen( $thistry ) != 6) { return false; } else { $thistry = intval ( $thistry ); } // If user is running in relaxed mode, we allow more time drifting // ±4 min, as opposed to ± 30 seconds in normal mode. if ( $relaxedmode == 'enabled' ) { $firstcount = -8; $lastcount = 8; } else { $firstcount = -1; $lastcount = 1; } $tm = floor( time() / 30 ); $secretkey=Base32::decode($secretkey); // Keys from 30 seconds before and after are valid aswell. for ($i=$firstcount; $i<=$lastcount; $i++) { // Pack time into binary string $time=chr(0).chr(0).chr(0).chr(0).pack('N*',$tm+$i); // Hash it with users secret key $hm = hash_hmac( 'SHA1', $time, $secretkey, true ); // Use last nipple of result as index/offset $offset = ord(substr($hm,-1)) & 0x0F; // grab 4 bytes of the result $hashpart=substr($hm,$offset,4); // Unpak binary value $value=unpack("N",$hashpart); $value=$value[1]; // Only 32 bits $value = $value & 0x7FFFFFFF; $value = $value % 1000000; if ( $value === $thistry ) { // Check for replay (Man-in-the-middle) attack. // Since this is not Star Trek, time can only move forward, // meaning current login attempt has to be in the future compared to // last successful login. if ( $lasttimeslot >= ($tm+$i) ) { error_log("Google Authenticator plugin: Man-in-the-middle attack detected (Could also be 2 legit login attempts within the same 30 second period)"); return false; } // Return timeslot in which login happened. return $tm+$i; } } return false; } /** * Create a new random secret for the Google Authenticator app. * 16 characters, randomly chosen from the allowed Base32 characters * equals 10 bytes = 80 bits, as 256^10 = 32^16 = 2^80 */ function create_secret() { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'; // allowed characters in Base32 $secret = ''; for ( $i = 0; $i < 16; $i++ ) { $secret .= substr( $chars, wp_rand( 0, strlen( $chars ) - 1 ), 1 ); } return $secret; } /** * Add the script to generate QR codes. */ function add_qrcode_script() { wp_enqueue_script('jquery'); wp_register_script('qrcode_script', plugins_url('jquery.qrcode.min.js', __FILE__),array("jquery")); wp_enqueue_script('qrcode_script'); } /** * Add 2fa pages to menus */ function add_pages() { // No menu entry for this page add_submenu_page( null, esc_html__( 'Google Authenticator', 'google-authenticator' ), null, 'read', self::SETUP_PAGE, array( $this, 'user_setup_page' ) ); // Site admin screen add_submenu_page( 'options-general.php', esc_html__( 'Google Authenticator', 'google-authenticator' ), esc_html__( 'Google Authenticator', 'google-authenticator' ), 'manage_options', 'google_authenticator', array( $this, 'admin_setup_page' ) ); // Network admin screen add_submenu_page( 'settings.php', esc_html__( 'Google Authenticator', 'google-authenticator' ), esc_html__( 'Google Authenticator', 'google-authenticator' ), 'manage_network_options', 'google_authenticator', array( $this, 'network_admin_setup_page' ) ); } /** * Determine if a user needs to setup authy 2fa * @return bool */ function user_needs_to_setup_google_authenticator() { $user = wp_get_current_user(); $enabled = trim(get_user_option( 'googleauthenticator_enabled', $user->ID ) ) === 'enabled'; if ( $enabled ) { return false; } $must_signup = false; $user_role = $user->roles[0]; $check_single_site_admin_options = true; if ( is_multisite() ) { $roles = get_site_option( 'googleauthenticator_mandatory_mfa_roles', array() ); if ( in_array( $user_role, $roles ) ) { $must_signup = true; } $check_single_site_admin_options = '1' !== get_site_option( 'googleauthenticator_network_only' ) ; } if ( ! $must_signup && $check_single_site_admin_options ) { $roles = get_option( 'googleauthenticator_mandatory_mfa_roles', array() ); if ( in_array( $user_role, $roles ) ) { $must_signup = true; } } return apply_filters( 'google_authenticator_needs_setup', $must_signup, $user ); } /** * Send users to the signup page if they must signup. */ function redirect_if_setup_required() { if ( $this->user_needs_to_setup_google_authenticator() ) { $screen = get_current_screen(); $pagename = 'admin_page_' . self::SETUP_PAGE; if ( is_a( $screen, 'WP_Screen') && in_array( $screen->id, array( $pagename, 'profile' ) ) ) { return; } // Some check against super admin so they can enable/disable the plugin. $location = admin_url( 'admin.php?page=' . self::SETUP_PAGE ); wp_redirect( $location); exit; } } /** * Save the GA secret if valid totp is provided * @return void */ function save_submitted_setup_page() { $this->error_message = null; // Reset a previous error message if it was set $user = wp_get_current_user(); $secret = empty( $_POST['GA_secret'] ) ? false : sanitize_text_field( $_POST['GA_secret']); $otp = empty( $_POST['GA_otp_code'] ) ? false : sanitize_text_field( $_POST['GA_otp_code']); if ( ! strlen( $secret ) || ! strlen( $otp ) ) { return; } $relaxed_mode = trim( get_user_option( 'googleauthenticator_relaxedmode', $user->ID ) ); $relaxed_mode = 'enabled' === $relaxed_mode ? 'enabled' : 'disabled'; if ( $timeslot = $this->verify( $secret, $otp, $relaxed_mode, '' ) ) { update_user_option( $user->ID, 'googleauthenticator_lasttimeslot', $timeslot, true ); update_user_option( $user->ID, 'googleauthenticator_secret', $secret, true ); update_user_option( $user->ID, 'googleauthenticator_enabled', 'enabled', true ); $location = admin_url( 'index.php?googleauthenticator=enabled' ); wp_redirect( $location ); exit; }; $this->error_message = new WP_Error( 'invalid-otp', esc_html__( "OTP code doesn't match supplied secret, please check you've configured Authenticator correctly.", 'google-authenticator' ) ); } /** * Show the user a success message after we redirect them following successful google authenticator setup */ function successful_signup_message() { if ( ! empty( $_GET['googleauthenticator'] ) && 'enabled' === $_GET['googleauthenticator'] ) : ?>
common_admin_setup_page(); } /** * Network admin setup screen */ function network_admin_setup_page() { $this->common_admin_setup_page( true ); } /** * Add verification code field to login form. */ function loginform() { echo "\t
\n"; echo "\t\t\n"; echo "\t
\n"; echo "\t\n"; } /** * Disable autocomplete on Google Authenticator code input field. */ function loginfooter() { echo "\n\n"; } /** * Login form handling. * Check Google Authenticator verification code, if user has been setup to do so. * @param wordpressuser / WP_Error * @return user/loginstatus */ function check_otp( $user, $username = '', $password = '' ) { // Store result of loginprocess, so far. $userstate = $user; // Get information on user, we need this in case an app password has been enabled, // since the $user var only contain an error at this point in the login flow. if ( get_user_by( 'email', $username ) === false ) { $user = get_user_by( 'login', $username ); } else { $user = get_user_by( 'email', $username ); } // Does the user have the Google Authenticator enabled ? if ( isset( $user->ID ) && trim(get_user_option( 'googleauthenticator_enabled', $user->ID ) ) == 'enabled' ) { // Get the users secret $GA_secret = trim( get_user_option( 'googleauthenticator_secret', $user->ID ) ); // Figure out if user is using relaxed mode ? $GA_relaxedmode = trim( get_user_option( 'googleauthenticator_relaxedmode', $user->ID ) ); // Get the verification code entered by the user trying to login if ( !empty( $_POST['googleotp'] )) { // Prevent PHP notices when using app password login $otp = trim( $_POST[ 'googleotp' ] ); } else { $otp = ''; } // When was the last successful login performed ? $lasttimeslot = trim( get_user_option( 'googleauthenticator_lasttimeslot', $user->ID ) ); // Valid code ? if ( $timeslot = $this->verify( $GA_secret, $otp, $GA_relaxedmode, $lasttimeslot ) ) { // Store the timeslot in which login was successful. update_user_option( $user->ID, 'googleauthenticator_lasttimeslot', $timeslot, true ); return $userstate; } else { // No, lets see if an app password is enabled, and this is an XMLRPC / APP login ? if ( trim( get_user_option( 'googleauthenticator_pwdenabled', $user->ID ) ) == 'enabled' && ( defined('XMLRPC_REQUEST') || defined('APP_REQUEST') ) ) { $GA_passwords = json_decode( get_user_option( 'googleauthenticator_passwords', $user->ID ) ); $passwordhash = trim($GA_passwords->{'password'} ); $usersha1 = sha1( strtoupper( str_replace( ' ', '', $password ) ) ); if ( $passwordhash == $usersha1 ) { // ToDo: Remove after some time when users have migrated to new format return new WP_User( $user->ID ); // Try the new version based on thee wp_hash_password function } elseif (wp_check_password( strtoupper( str_replace( ' ', '', $password ) ), $passwordhash)) { return new WP_User( $user->ID ); } else { // Wrong XMLRPC/APP password ! return new WP_Error( 'invalid_google_authenticator_password', __( 'ERROR: The Google Authenticator password is incorrect.', 'google-authenticator' ) ); } } else { if ( ! $this->is_two_screen_signin_enabled() ) { return new WP_Error( 'invalid_google_authenticator_token', __( 'ERROR: The Google Authenticator code is incorrect or has expired.', 'google-authenticator' ) ); } else { wp_logout(); $this->secondary_login_screen(); exit; } } } } // Google Authenticator isn't enabled for this account, // just resume normal authentication. return $userstate; } function secondary_login_screen() { $redirect_to = isset( $_REQUEST['redirect_to'] ) ? $_REQUEST['redirect_to'] : admin_url(); login_header( esc_html__('Secondary Login Screen', 'google-authenticator' ) ); if ( array_key_exists( 'googleotp', $_REQUEST ) ) { if ( 0 === strlen( $_REQUEST[ 'googleotp'] ) ) { $error_message = __( 'ERROR: The Google Authenticator code is missing.', 'google-authenticator' ); } else { $error_message = __( 'ERROR: The Google Authenticator code is incorrect or has expired.', 'google-authenticator' ); } echo '| ".__( 'Active', 'google-authenticator' )." | \n"; echo "\n"; echo "\n"; echo " | \n"; echo "
|---|---|
| " . __( 'Relaxed mode', 'google-authenticator' ) . " | \n"; echo "\n"; echo "" . __( ' Relaxed mode allows for more time drifting on your phone clock (±4 min).', 'google-authenticator' ) . "\n"; echo " | \n"; echo "
| \n"; echo " | " . __( ' Description that you\'ll see in the Google Authenticator app on your phone.', 'google-authenticator' ) . " | \n";
echo "
| \n"; echo " | \n"; echo ""; if ( $args['show_secret_buttons']) { echo ""; echo ""; } echo " | \n"; echo "
| \n"; $qr_style = $args['show_secret_qr'] ? '' : 'display: none'; echo " | ";
echo "";
echo ' ' . __( 'Scan this with the Google Authenticator app.', 'google-authenticator' ) . ''; echo " | \n";
echo "".__( 'Enable App password', 'google-authenticator' )." | \n"; echo "\n"; echo "".__(' Enabling an App password will decrease your overall login security.','google-authenticator')."\n"; echo " | \n"; echo "\n"; echo "
| \n"; echo " | \n"; echo ""; echo ""; echo " ".__(' Password is not stored in cleartext, this is your only chance to see it.','google-authenticator')."\n"; echo " | \n"; echo "
| \n"; echo " | " . __( 'After adding the site to your google authy account, add your authenticator code here.', 'google-authenticator' ) . " | \n";
echo "
| ".__('Hide settings from user','google-authenticator')." | \n"; echo "\n";
echo " \n";
echo " | \n";
echo "
|---|---|
| ".__('Active','google-authenticator')." | \n"; echo "\n";
echo " \n";
echo " | \n";
echo "