Authy two-factor authentication to WordPress. Users opt in for an added level of security that relies on random codes from their mobile devices. * Author: Erick Hitter * Version: 0.1 * Author URI: http://www.ethitter.com/ * License: GPL2+ * Text Domain: authy_for_wp This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ class Authy_WP { /** * Class variables */ // Oh look, a singleton private static $__instance = null; // Some plugin info protected $name = 'Authy for WordPress'; // Parsed settings private $settings = null; // Is API ready, should plugin act? protected $ready = false; // Authy API protected $api = null; protected $api_key = null; protected $api_endpoint = null; // Interface keys protected $settings_page = 'authy-for-wp'; protected $users_page = 'authy-for-wp-user'; // Data storage keys protected $settings_key = 'authy_for_wp'; protected $users_key = 'authy_for_wp_user'; // Settings field placeholders protected $settings_fields = array(); protected $settings_field_defaults = array( 'label' => null, 'type' => 'text', 'sanitizer' => 'sanitize_text_field', 'section' => 'default', 'class' => null ); // Default Authy data protected $user_defaults = array( 'email' => null, 'phone' => null, 'country_code' => '+1', 'authy_id' => null ); /** * Singleton implementation * * @uses this::setup * @return object */ public static function instance() { if ( ! is_a( self::$__instance, 'Authy_WP' ) ) { self::$__instance = new Authy_WP; self::$__instance->setup(); } return self::$__instance; } /** * Silence is golden. */ private function __construct() {} /** * Plugin setup * * @uses this::register_settings_fields, this::prepare_api, add_action, add_filter * @return null */ private function setup() { require( 'authy-wp-api.php' ); $this->register_settings_fields(); $this->prepare_api(); // Plugin settings add_action( 'admin_init', array( $this, 'action_admin_init' ) ); add_action( 'admin_menu', array( $this, 'action_admin_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'action_admin_enqueue_scripts' ) ); add_filter( 'plugin_action_links', array( $this, 'filter_plugin_action_links' ), 10, 2 ); // Anything other than plugin configuration belongs in here. if ( $this->ready ) { // User settings add_action( 'show_user_profile', array( $this, 'action_show_user_profile' ) ); add_action( 'edit_user_profile', array( $this, 'action_edit_user_profile' ) ); add_action( 'wp_ajax_' . $this->users_page, array( $this, 'ajax_get_id' ) ); add_action( 'personal_options_update', array( $this, 'action_personal_options_update' ) ); add_action( 'edit_user_profile_update', array( $this, 'action_edit_user_profile_update' ) ); // Authentication add_action( 'login_form', array( $this, 'action_login_form' ), 50 ); add_filter( 'authenticate', array( $this, 'action_authenticate' ), 9999, 2 ); } } /** * Add settings fields for main plugin page * * @uses __ * @return null */ protected function register_settings_fields() { $this->settings_fields = array( array( 'name' => 'api_key_production', 'label' => __( 'Production API Key', 'authy_wp' ), 'type' => 'text', 'sanitizer' => 'alphanumeric' ), array( 'name' => 'api_key_development', 'label' => __( 'Development API Key', 'authy_wp' ), 'type' => 'text', 'sanitizer' => 'alphanumeric' ) ); } /** * Set class variables regarding API * Instantiates the Authy API class into $this->api * * @uses this::get_setting, Authy_WP_API::instance */ protected function prepare_api() { $endpoints = array( 'production' => 'https://api.authy.com', 'development' => 'http://sandbox-api.authy.com' ); // Plugin page accepts keys for production and development. // Cannot be toggled except via the `authy_wp_environment` filter. $environment = $this->get_setting( 'environment' ); // API key is specific to the environment $api_key = $this->get_setting( 'api_key_' . $environment ); // Only prepare the API endpoint if we have all information needed. if ( $api_key && isset( $endpoints[ $environment ] ) ) { $this->api_key = $api_key; $this->api_endpoint = $endpoints[ $environment ]; $this->ready = true; } // Instantiate the API class $this->api = Authy_WP_API::instance( $this->api_key, $this->api_endpoint ); } /** * COMMON PLUGIN ELEMENTS */ /** * Register plugin's setting and validation callback * * @param action admin_init * @uses register_setting * @return null */ public function action_admin_init() { register_setting( $this->settings_page, $this->settings_key, array( $this, 'validate_plugin_settings' ) ); } /** * Register plugin settings page and page's sections * * @uses add_options_page, add_settings_section * @action admin_menu * @return null */ public function action_admin_menu() { add_options_page( $this->name, 'Authy for WP', 'manage_options', $this->settings_page, array( $this, 'plugin_settings_page' ) ); add_settings_section( 'default', '', array( $this, 'register_settings_page_sections' ), $this->settings_page ); } /** * Enqueue admin script for connection modal * * @uses get_current_screen, wp_enqueue_script, plugins_url, wp_localize_script, this::get_ajax_url, wp_enqueue_style * @action admin_enqueue_scripts * @return null */ public function action_admin_enqueue_scripts() { if ( ! $this->ready ) return; $current_screen = get_current_screen(); if ( 'profile' == $current_screen->base ) { wp_enqueue_script( 'authy-wp-profile', plugins_url( 'assets/authy-wp-profile.js', __FILE__ ), array( 'jquery', 'thickbox' ), 1.01, true ); wp_localize_script( 'authy-wp-profile', 'AuthyForWP', array( 'ajax' => $this->get_ajax_url(), 'th_text' => __( 'Connection', 'authy_for_wp' ), 'button_text' => __( 'Manage Authy Connection', 'authy_for_wp' ) ) ); wp_enqueue_style( 'thickbox' ); } } /** * Add settings link to plugin row actions * * @param array $links * @param string $plugin_file * @uses menu_page_url, __ * @filter plugin_action_links * @return array */ public function filter_plugin_action_links( $links, $plugin_file ) { if ( false !== strpos( $plugin_file, pathinfo( __FILE__, PATHINFO_FILENAME ) ) ) $links['settings'] = '' . __( 'Settings', 'authy_for_wp' ) . ''; return $links; } /** * Retrieve a plugin setting * * @param string $key * @uses get_option, wp_parse_args, apply_filters * @return array or false */ public function get_setting( $key ) { $value = false; if ( is_null( $this->settings ) || ! is_array( $this->settings ) ) { $this->settings = get_option( $this->settings_key ); $this->settings = wp_parse_args( $this->settings, array( 'api_key_production' => '', 'api_key_development' => '', 'environment' => apply_filters( 'authy_wp_environment', 'production' ) ) ); } if ( isset( $this->settings[ $key ] ) ) $value = $this->settings[ $key ]; return $value; } /** * Build Ajax URL for users' connection management * * @uses add_query_arg, wp_create_nonce, admin_url * @return string */ protected function get_ajax_url() { return add_query_arg( array( 'action' => $this->users_page, 'nonce' => wp_create_nonce( $this->users_key . '_ajax' ) ), admin_url( 'admin-ajax.php' ) ); } /** * GENERAL OPTIONS PAGE */ /** * Populate settings page's sections * * @uses wp_parse_args, add_settings_field * @return null */ public function register_settings_page_sections() { foreach ( $this->settings_fields as $args ) { $args = wp_parse_args( $args, $this->settings_field_defaults ); add_settings_field( $args['name'], $args['label'], array( $this, 'form_field_' . $args['type'] ), $this->settings_page, $args['section'], $args ); } } /** * Render text input * * @param array $args * @uses wp_parse_args, esc_attr, this::get_setting, esc_attr * @return string or null */ public function form_field_text( $args ) { $args = wp_parse_args( $args, $this->settings_field_defaults ); $name = esc_attr( $args['name'] ); if ( empty( $name ) ) return; if ( is_null( $args['class'] ) ) $args['class'] = 'regular-text'; $value = $this->get_setting( $args['name'] ); ?>
%1$s and create an application for access to the Authy API.', 'authy_for_wp' ), 'http://www.authy.com/' ); ?>
| users_key . 'disable_own', $this->users_key . '[nonce]' ); ?> | |
| users_key . 'edit_own', $this->users_key . '[nonce]' ); ?> | |
user_has_authy_id( $user->ID ) ) return $user; // If a user has opted in, he/she must provide a token if ( ! isset( $_POST['authy_token'] ) || empty( $_POST['authy_token'] ) ) return new WP_Error( 'authentication_failed', sprintf( __('ERROR: To log in as %s, you must provide an Authy token.'), $username ) ); // Check the specified token $authy_id = $this->get_user_authy_id( $user->ID ); $authy_token = preg_replace( '#[^\d]#', '', $_POST['authy_token'] ); $api_check = $this->api->check_token( $authy_id, $authy_token ); // Act on API response if ( false === $api_check ) return null; elseif ( is_string( $api_check ) ) return new WP_Error( 'authentication_failed', __('ERROR: ' . $api_check ) ); return $user; } } Authy_WP::instance();