Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Two Factor User Profile Shortcode #261

Closed
wants to merge 9 commits into from
249 changes: 136 additions & 113 deletions class.two-factor-core.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,17 @@ class Two_Factor_Core {
public static function add_hooks() {
add_action( 'plugins_loaded', array( __CLASS__, 'load_textdomain' ) );
add_action( 'init', array( __CLASS__, 'get_providers' ) );
add_action( 'init', array( __CLASS__, 'user_two_factor_process_update' ) );
add_action( 'wp_login', array( __CLASS__, 'wp_login' ), 10, 2 );
add_action( 'login_form_validate_2fa', array( __CLASS__, 'login_form_validate_2fa' ) );
add_action( 'login_form_backup_2fa', array( __CLASS__, 'backup_2fa' ) );
add_action( 'show_user_profile', array( __CLASS__, 'user_two_factor_options' ) );
add_action( 'edit_user_profile', array( __CLASS__, 'user_two_factor_options' ) );
add_action( 'personal_options_update', array( __CLASS__, 'user_two_factor_options_update' ) );
add_action( 'edit_user_profile_update', array( __CLASS__, 'user_two_factor_options_update' ) );
add_filter( 'manage_users_columns', array( __CLASS__, 'filter_manage_users_columns' ) );
add_filter( 'wpmu_users_columns', array( __CLASS__, 'filter_manage_users_columns' ) );
add_filter( 'manage_users_custom_column', array( __CLASS__, 'manage_users_custom_column' ), 10, 3 );

add_shortcode( 'two-factor-user-profile', array( __CLASS__, 'user_profile_shortcode' ) );
}

/**
Expand Down Expand Up @@ -116,40 +117,22 @@ public static function get_providers() {
return $providers;
}

/**
* Get all Two-Factor Auth providers that are enabled for the specified|current user.
*
* @param WP_User $user WP_User object of the logged-in user.
* @return array
*/
public static function get_enabled_providers_for_user( $user = null ) {
if ( empty( $user ) || ! is_a( $user, 'WP_User' ) ) {
$user = wp_get_current_user();
}
public static function get_enabled_providers_for_user( $user_id ) {
$enabled_providers = get_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, true );

$providers = self::get_providers();
$enabled_providers = get_user_meta( $user->ID, self::ENABLED_PROVIDERS_USER_META_KEY, true );
if ( empty( $enabled_providers ) ) {
if ( ! is_array( $enabled_providers ) || empty( $enabled_providers ) ) {
$enabled_providers = array();
}
$enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );

return $enabled_providers;
return array_intersect(
$enabled_providers,
array_keys( self::get_providers() )
);
}

/**
* Get all Two-Factor Auth providers that are both enabled and configured for the specified|current user.
*
* @param WP_User $user WP_User object of the logged-in user.
* @return array
*/
public static function get_available_providers_for_user( $user = null ) {
if ( empty( $user ) || ! is_a( $user, 'WP_User' ) ) {
$user = wp_get_current_user();
}

$providers = self::get_providers();
$enabled_providers = self::get_enabled_providers_for_user( $user );
public static function get_available_providers_for_user( $user ) {
$providers = self::get_providers();
$enabled_providers = self::get_enabled_providers_for_user( $user->ID );
$configured_providers = array();

foreach ( $providers as $classname => $provider ) {
Expand All @@ -161,21 +144,34 @@ public static function get_available_providers_for_user( $user = null ) {
return $configured_providers;
}

/**
* Gets the Two-Factor Auth provider for the specified|current user.
*
* @since 0.1-dev
*
* @param int $user_id Optional. User ID. Default is 'null'.
* @return object|null
*/
public static function get_primary_provider_for_user( $user_id = null ) {
if ( empty( $user_id ) || ! is_numeric( $user_id ) ) {
$user_id = get_current_user_id();
protected static function user_set_enabled_providers( $user_id, $providers ) {
// Enable only the available providers.
$providers = array_intersect(
$providers,
array_keys( self::get_providers() )
);

return update_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, $providers );
}

protected static function user_set_primary_provider( $user_id, $provider ) {
$providers_enabled = self::get_enabled_providers_for_user( $user_id );

if ( in_array( $provider, $providers_enabled, true ) ) {
return update_user_meta( $user_id, self::PROVIDER_USER_META_KEY, $provider );
}

$providers = self::get_providers();
$available_providers = self::get_available_providers_for_user( get_userdata( $user_id ) );
return false;
}

public static function get_primary_provider_for_user( $user_id ) {
$user = get_userdata( $user_id );

if ( ! isset( $user->ID ) ) {
return null;
}

$available_providers = self::get_available_providers_for_user( $user );

// If there's only one available provider, force that to be the primary.
if ( empty( $available_providers ) ) {
Expand All @@ -199,6 +195,8 @@ public static function get_primary_provider_for_user( $user_id = null ) {
*/
$provider = apply_filters( 'two_factor_primary_provider_for_user', $provider, $user_id );

$providers = self::get_providers();

if ( isset( $providers[ $provider ] ) ) {
return $providers[ $provider ];
}
Expand All @@ -214,11 +212,43 @@ public static function get_primary_provider_for_user( $user_id = null ) {
* @param int $user_id Optional. User ID. Default is 'null'.
* @return bool
*/
public static function is_user_using_two_factor( $user_id = null ) {
public static function is_user_using_two_factor( $user_id ) {
$provider = self::get_primary_provider_for_user( $user_id );

return ! empty( $provider );
}

/**
* Render the user settings via shortcode.
*
* @param array $args Shortcode arguments.
*
* @return void
*/
public static function user_profile_shortcode( $args ) {
// Only logged-in users can edit things.
if ( ! is_user_logged_in() ) {
return null;
}

$user = wp_get_current_user();

?>
<form method="post" class="two-factor-user-settings two-factor-user-settings-shortcode">
<?php
self::user_two_factor_providers_table( $user );
submit_button();
?>
</form>
<?php
}

public function user_two_factor_process_update() {
if ( is_user_logged_in() && isset( $_POST['_nonce_user_two_factor_options'] ) ) {
self::user_two_factor_options_update( get_current_user_id() );
}
}

/**
* Handle the browser-based login.
*
Expand Down Expand Up @@ -668,93 +698,86 @@ public static function manage_users_custom_column( $output, $column_name, $user_
* @param WP_User $user WP_User object of the logged-in user.
*/
public static function user_two_factor_options( $user ) {
wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ) );

$enabled_providers = array_keys( self::get_available_providers_for_user( $user ) );
$primary_provider = self::get_primary_provider_for_user( $user->ID );

if ( ! empty( $primary_provider ) && is_object( $primary_provider ) ) {
$primary_provider_key = get_class( $primary_provider );
} else {
$primary_provider_key = null;
}

wp_nonce_field( 'user_two_factor_options', '_nonce_user_two_factor_options', false );

?>
<input type="hidden" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php /* Dummy input so $_POST value is passed when no providers are enabled. */ ?>" />
<table class="form-table">
<tr>
<th>
<?php esc_html_e( 'Two-Factor Options' ); ?>
</th>
<td>
<table class="two-factor-methods-table">
<thead>
<tr>
<th class="col-enabled" scope="col"><?php esc_html_e( 'Enabled', 'two-factor' ); ?></th>
<th class="col-primary" scope="col"><?php esc_html_e( 'Primary', 'two-factor' ); ?></th>
<th class="col-name" scope="col"><?php esc_html_e( 'Name', 'two-factor' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( self::get_providers() as $class => $object ) : ?>
<tr>
<th scope="row"><input type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $class ); ?>" <?php checked( in_array( $class, $enabled_providers ) ); ?> /></th>
<th scope="row"><input type="radio" name="<?php echo esc_attr( self::PROVIDER_USER_META_KEY ); ?>" value="<?php echo esc_attr( $class ); ?>" <?php checked( $class, $primary_provider_key ); ?> /></th>
<td>
<?php $object->print_label(); ?>
<?php do_action( 'two-factor-user-options-' . $class, $user ); ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php self::user_two_factor_providers_table( $user ); ?>
</td>
</tr>
</table>
<?php
/**
* Fires after the Two Factor methods table.
*
* To be used by Two Factor methods to add settings UI.
*
* @since 0.1-dev
*/
do_action( 'show_user_security_settings', $user );
}

/**
* Update the user meta value.
*
* This executes during the `personal_options_update` & `edit_user_profile_update` actions.
*
* @since 0.1-dev
*
* @param int $user_id User ID.
*/
public static function user_two_factor_options_update( $user_id ) {
if ( isset( $_POST['_nonce_user_two_factor_options'] ) ) {
check_admin_referer( 'user_two_factor_options', '_nonce_user_two_factor_options' );
protected function user_two_factor_providers_table( $user ) {
// Ensure we can render things outside the WP admin.
require_once ABSPATH . 'wp-admin/includes/admin.php';

if ( ! isset( $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ] ) ||
! is_array( $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ] ) ) {
return;
}
$enabled_providers = array_keys( self::get_available_providers_for_user( $user ) );
$primary_provider = self::get_primary_provider_for_user( $user->ID );
$primary_provider_key = null;

$providers = self::get_providers();
if ( ! empty( $primary_provider ) && is_object( $primary_provider ) ) {
$primary_provider_key = get_class( $primary_provider );
}

$enabled_providers = $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ];
wp_enqueue_style( 'user-edit-2fa', plugins_url( 'user-edit.css', __FILE__ ) );

// Enable only the available providers.
$enabled_providers = array_intersect( $enabled_providers, array_keys( $providers ) );
update_user_meta( $user_id, self::ENABLED_PROVIDERS_USER_META_KEY, $enabled_providers );
wp_nonce_field( 'user_two_factor_options', '_nonce_user_two_factor_options', false );

// Primary provider must be enabled.
$new_provider = isset( $_POST[ self::PROVIDER_USER_META_KEY ] ) ? $_POST[ self::PROVIDER_USER_META_KEY ] : '';
if ( ! empty( $new_provider ) && in_array( $new_provider, $enabled_providers, true ) ) {
update_user_meta( $user_id, self::PROVIDER_USER_META_KEY, $new_provider );
}
?>
<input type="hidden" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="" />
<table class="two-factor-methods-table">
<thead>
<tr>
<th class="col-enabled" scope="col"><?php esc_html_e( 'Enabled', 'two-factor' ); ?></th>
<th class="col-primary" scope="col"><?php esc_html_e( 'Primary', 'two-factor' ); ?></th>
<th class="col-name" scope="col"><?php esc_html_e( 'Name', 'two-factor' ); ?></th>
</tr>
</thead>
<tbody>
<?php foreach ( self::get_providers() as $class => $object ) : ?>
<tr>
<th scope="row"><input type="checkbox" name="<?php echo esc_attr( self::ENABLED_PROVIDERS_USER_META_KEY ); ?>[]" value="<?php echo esc_attr( $class ); ?>" <?php checked( in_array( $class, $enabled_providers ) ); ?> /></th>
<th scope="row"><input type="radio" name="<?php echo esc_attr( self::PROVIDER_USER_META_KEY ); ?>" value="<?php echo esc_attr( $class ); ?>" <?php checked( $class, $primary_provider_key ); ?> /></th>
<td>
<p>
<strong><?php $object->print_label(); ?></strong>
</p>
<?php
$object->render_user_settings( $user );
do_action( 'two-factor-user-options-' . $class, $user );
?>
</td>
</tr>
<?php endforeach; ?>
<?php
/**
* Fires after the Two Factor methods table.
*
* To be used by Two Factor methods to add settings UI.
*
* @since 0.1-dev
*/
do_action( 'show_user_security_settings', $user );
?>
</tbody>
</table>
<?php
}

protected static function user_two_factor_options_update( $user_id ) {
check_admin_referer( 'user_two_factor_options', '_nonce_user_two_factor_options' );

if ( isset( $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ] ) ) {
self::user_set_enabled_providers( $user_id, $_POST[ self::ENABLED_PROVIDERS_USER_META_KEY ] );
}

if ( isset( $_POST[ self::PROVIDER_USER_META_KEY ] ) ) {
self::user_set_primary_provider( $user, $_POST[ self::PROVIDER_USER_META_KEY ] );
}
}
}
14 changes: 0 additions & 14 deletions providers/class.two-factor-backup-codes.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,6 @@ class Two_Factor_Backup_Codes extends Two_Factor_Provider {
*/
const NUMBER_OF_CODES = 10;

/**
* Ensures only one instance of this class exists in memory at any one time.
*
* @since 0.1-dev
*/
static function get_instance() {
static $instance;
$class = __CLASS__;
if ( ! is_a( $instance, $class ) ) {
$instance = new $class;
}
return $instance;
}

/**
* Class constructor.
*
Expand Down
14 changes: 0 additions & 14 deletions providers/class.two-factor-dummy.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,6 @@
*/
class Two_Factor_Dummy extends Two_Factor_Provider {

/**
* Ensures only one instance of this class exists in memory at any one time.
*
* @since 0.1-dev
*/
static function get_instance() {
static $instance;
$class = __CLASS__;
if ( ! is_a( $instance, $class ) ) {
$instance = new $class;
}
return $instance;
}

/**
* Class constructor.
*
Expand Down
14 changes: 0 additions & 14 deletions providers/class.two-factor-email.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,6 @@ class Two_Factor_Email extends Two_Factor_Provider {
*/
const INPUT_NAME_RESEND_CODE = 'two-factor-email-code-resend';

/**
* Ensures only one instance of this class exists in memory at any one time.
*
* @since 0.1-dev
*/
static function get_instance() {
static $instance;
$class = __CLASS__;
if ( ! is_a( $instance, $class ) ) {
$instance = new $class;
}
return $instance;
}

/**
* Class constructor.
*
Expand Down
Loading