From 2d6a50751a22ab8508760f954b92f13e2029aae1 Mon Sep 17 00:00:00 2001 From: JJ Date: Tue, 20 Aug 2024 15:29:31 -0400 Subject: [PATCH 01/10] wip: first pass with actions from dd32 --- classes/class-connectors.php | 1 + composer.json | 1 + composer.lock | 20 +- connectors/class-connector-two-factor.php | 311 ++++++++++++++++++++++ 4 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 connectors/class-connector-two-factor.php diff --git a/classes/class-connectors.php b/classes/class-connectors.php index 5af5469c1..42d732258 100644 --- a/classes/class-connectors.php +++ b/classes/class-connectors.php @@ -93,6 +93,7 @@ public function load_connectors() { 'gravityforms', 'jetpack', 'mercator', + 'two-factor', 'user-switching', 'woocommerce', 'wordpress-seo', diff --git a/composer.json b/composer.json index 52183ae60..96ec62190 100644 --- a/composer.json +++ b/composer.json @@ -33,6 +33,7 @@ "wpackagist-plugin/classic-editor": "1.6.4", "wpackagist-plugin/easy-digital-downloads": "3.3.1", "wpackagist-plugin/jetpack": "13.6", + "wpackagist-plugin/two-factor": "0.9.1", "wpackagist-plugin/user-switching": "1.8.0", "wpackagist-plugin/wordpress-seo": "23.1", "wpackagist-plugin/wp-crontrol": "1.17.0", diff --git a/composer.lock b/composer.lock index 339b8eac8..2b732b7b8 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "10410efd54bb48d70f99c5e81ba94947", + "content-hash": "2722abeae3b9ae8df5667b69b758c00c", "packages": [ { "name": "composer/installers", @@ -8522,6 +8522,24 @@ "type": "wordpress-plugin", "homepage": "https://wordpress.org/plugins/jetpack/" }, + { + "name": "wpackagist-plugin/two-factor", + "version": "0.9.1", + "source": { + "type": "svn", + "url": "https://plugins.svn.wordpress.org/two-factor/", + "reference": "tags/0.9.1" + }, + "dist": { + "type": "zip", + "url": "https://downloads.wordpress.org/plugin/two-factor.0.9.1.zip" + }, + "require": { + "composer/installers": "^1.0 || ^2.0" + }, + "type": "wordpress-plugin", + "homepage": "https://wordpress.org/plugins/two-factor/" + }, { "name": "wpackagist-plugin/user-switching", "version": "1.8.0", diff --git a/connectors/class-connector-two-factor.php b/connectors/class-connector-two-factor.php new file mode 100644 index 000000000..77b767b6c --- /dev/null +++ b/connectors/class-connector-two-factor.php @@ -0,0 +1,311 @@ + esc_html_x( 'Enabled', 'two-factor', 'stream' ), + 'disabled' => esc_html_x( 'Disabled', 'two-factor', 'stream' ), + 'recovered' => esc_html_x( 'Recovered', 'two-factor', 'stream' ), + 'updated' => esc_html_x( 'Updated', 'two-factor', 'stream' ), + 'removed' => esc_html_x( 'Removed', 'two-factor', 'stream' ), + 'added' => esc_html_x( 'Added', 'two-factor', 'stream' ), + 'authenticated' => esc_html_x( 'Authenticated', 'two-factor', 'stream' ), + ); + } + + /** + * Return translated context labels + * + * @return array Context label translations + */ + public function get_context_labels() { + return array( + 'user-settings' => esc_html_x( 'User Settings', 'two-factor', 'stream' ), + 'auth' => esc_html_x( 'Authentications', 'two-factor', 'stream' ), + ); + } + + /** + * Register the connector + */ + public function register() { + parent::register(); + + add_filter( 'wp_stream_log_data', [ $this, 'log_override' ] ); + } + + /** + * Modify or prevent logging of some actions. + * + * @param array $data Record data. + * + * @return array|bool + */ + public function log_override( $data ) { + if ( ! is_array( $data ) ) { + return $data; + } + + // If a login was made but no cookies are being sent (ie. hit the 2FA interstitial), don't log it. + if ( + 'users' === $data['connector'] && + 'sessions' === $data['context'] && + 'login' === $data['action'] && + \Two_Factor_Core::is_user_using_two_factor( $data['user_id'] ) && + 'set_logged_in_cookie' === current_filter() && + has_filter( 'send_auth_cookies', '__return_false' ) + ) { + $data = false; + } + + return $data; + } + + /** + * Callback to watch for 2FA authenticated actions. + * + * @param \WP_User $user Authenticated user. + * @param object $provider The 2FA Provider used. + */ + public function callback_two_factor_user_authenticated( $user, $provider ) { + $this->log( + 'Authenticated via %s', + array( + 'provider' => $provider->get_key(), + ), + $user->ID, + 'auth', + 'authenticated', + $user->ID + ); + } + + /** + * Callback to watch for failed logins with Two Factor errors. + * + * @param string $user_login User login. + * @param \WP_Error $error WP_Error object. + */ + public function callback_wp_login_failed( $user_login, $error ) { + if ( ! str_starts_with( $error->get_error_code(), 'two_factor_' ) ) { + return; + } + + $user = get_user_by( 'login', $user_login ); + if ( ! $user && is_email( $user_login ) ) { + $user = get_user_by( 'email', $user_login ); + } + + $this->log( + '%s Failed 2FA: %s %s', + array( + 'display_name' => $user->display_name, + 'code' => $error->get_error_code(), + 'error' => $error->get_error_message(), + ), + $user->ID, + 'auth', + 'failed', + $user->ID + ); + } + + /** + * Callback to watch for user_meta changes BEFORE it's made. + * + * @param int $meta_id Meta ID. + * @param int $user_id User ID. + * @param string $meta_key Meta key. + * @param mixed $new_meta_value The NEW meta value. + */ + public function callback_update_user_meta( $meta_id, $user_id, $meta_key, $new_meta_value ) { + unset( $meta_id ); + unset( $new_meta_value ); + + switch( $meta_key ) { + case '_two_factor_backup_codes': + case '_two_factor_totp_key': + case '_two_factor_enabled_providers': + $this->user_meta[ $user_id ][ $meta_key ] = get_user_meta( $user_id, $meta_key, true ); + break; + } + } + + /** + * Callback to watch for user_meta changes AFTER it's made. + * + * @param int $meta_id Meta ID. + * @param int $user_id User ID. + * @param string $meta_key Meta key. + * @param mixed $new_meta_value The NEW meta value. + */ + public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_meta_value ) { + unset( $meta_id ); + + $old_meta_value = $this->user_meta[ $user_id ][ $meta_key ] ?? null; + unset( $this->user_meta[ $user_id ][ $meta_key ] ); + + switch( $meta_key ) { + case '_two_factor_backup_codes': + $this->log( + esc_html__( 'Updated backup codes', 'stream' ), + array(), + $user_id, + 'user-settings', + 'updated' + ); + break; + case '_two_factor_totp_key': + $this->log( + esc_html__( 'Set TOTP secret key' ), + array(), + $user_id, + 'user-settings', + 'updated' + ); + break; + case '_two_factor_enabled_providers': + $old_providers = $old_meta_value ?? []; + $new_providers = $new_meta_value ?? []; + + $enabled_providers = array_diff( $new_providers, $old_providers ); + $disabled_providers = array_diff( $old_providers, $new_providers ); + + foreach ( $enabled_providers as $provider ) { + $this->log( + /* Translators: %s is the provider */ + esc_html__( 'Enabled provider: %s', 'stream' ), + array( + 'provider' => $provider, + ), + $user_id, + 'user-settings', + 'enabled' + ); + } + + foreach ( $disabled_providers as $provider ) { + $this->log( + /* Translators: %s is the provider */ + esc_html__( 'Disabled provider: %s', 'stream' ), + array( + 'provider' => $provider, + ), + $user_id, + 'user-settings', + 'disabled' + ); + } + break; + } + } + + public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_value ) { + unset( $meta_id ); + + switch( $meta_key ) { + case '_two_factor_backup_codes': + $this->log( + esc_html__( 'Added backup codes', 'stream' ), + array(), + $user_id, + 'user-settings', + 'updated' + ); + break; + case '_two_factor_totp_key': + $this->log( + esc_html__( 'Set TOTP secret key' ), + array(), + $user_id, + 'user-settings', + 'updated' + ); + break; + case '_two_factor_enabled_providers': + foreach ( $meta_value as $provider ) { + $this->log( + /* Translators: %s is the provider */ + esc_html__( 'Enabled provider: %s', 'stream' ), + array( + 'provider' => $provider, + ), + $user_id, + 'user-settings', + 'enabled' + ); + } + break; + } + } +} From f59911d1820ce9d2f014e6234761a95909e20591 Mon Sep 17 00:00:00 2001 From: JJ Date: Wed, 21 Aug 2024 17:02:00 -0400 Subject: [PATCH 02/10] The htaccess rules were a bit off for subdirectory installations This needs local testing but it sorts some scripts and styles which had 500 errors. --- local/public/.htaccess | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/local/public/.htaccess b/local/public/.htaccess index 632d57eab..0ee6ebd51 100644 --- a/local/public/.htaccess +++ b/local/public/.htaccess @@ -11,7 +11,8 @@ RewriteRule ^([_0-9a-zA-Z-]+/)?wp-admin$ $1wp-admin/ [R=301,L] RewriteCond %{REQUEST_FILENAME} -f [OR] RewriteCond %{REQUEST_FILENAME} -d RewriteRule ^ - [L] -RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(content|admin|includes).*) /wp/$2 [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-(admin|includes).*) /wp/$2 [L] +RewriteRule ^([_0-9a-zA-Z-]+/)?(wp-content.*) /$2 [L] RewriteRule ^([_0-9a-zA-Z-]+/)?(.*\.php)$ /wp/$2 [L] RewriteRule . /wp/index.php [L] # END WordPress From d2651576204599cd229655a88dcc01d2399991d5 Mon Sep 17 00:00:00 2001 From: JJ Date: Thu, 22 Aug 2024 09:53:15 -0400 Subject: [PATCH 03/10] working through how to not duplicate profile updates --- connectors/class-connector-two-factor.php | 15 +++++++++++---- connectors/class-connector-users.php | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/connectors/class-connector-two-factor.php b/connectors/class-connector-two-factor.php index 77b767b6c..025e332e7 100644 --- a/connectors/class-connector-two-factor.php +++ b/connectors/class-connector-two-factor.php @@ -269,7 +269,14 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ break; } } - + /** + * Callback to watch for user_meta changes AFTER it's added. + * + * @param int $meta_id Meta ID. + * @param int $user_id User ID. + * @param string $meta_key Meta key. + * @param mixed $meta_value Meta value. + */ public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_value ) { unset( $meta_id ); @@ -280,16 +287,16 @@ public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_ array(), $user_id, 'user-settings', - 'updated' + 'added' ); break; case '_two_factor_totp_key': $this->log( - esc_html__( 'Set TOTP secret key' ), + esc_html__( 'Added TOTP secret key' ), array(), $user_id, 'user-settings', - 'updated' + 'added' ); break; case '_two_factor_enabled_providers': diff --git a/connectors/class-connector-users.php b/connectors/class-connector-users.php index 91b6287e7..6eb24357f 100644 --- a/connectors/class-connector-users.php +++ b/connectors/class-connector-users.php @@ -182,7 +182,7 @@ public function callback_user_register( $user_id ) { * @param int $user_id Registered user ID. * @param \WP_User $user Registered user object. */ - public function callback_profile_update( $user_id, $user ) { + public function callback_profile_update( $user_id, $user, $new_userdata ) { unset( $user_id ); $this->log( From e8e737cdaa641b7598f6552b5d972865a48ebf5d Mon Sep 17 00:00:00 2001 From: JJ Date: Thu, 22 Aug 2024 15:11:58 -0400 Subject: [PATCH 04/10] stop recording login before actual login --- classes/class-connectors.php | 2 +- connectors/class-connector-two-factor.php | 27 ++++++++++++----------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/classes/class-connectors.php b/classes/class-connectors.php index 42d732258..df01b1cda 100644 --- a/classes/class-connectors.php +++ b/classes/class-connectors.php @@ -116,7 +116,7 @@ public function load_connectors() { } // Initialize connector. - $class = new $class_name(); + $class = new $class_name(); $classes[ $class->name ] = $class; } diff --git a/connectors/class-connector-two-factor.php b/connectors/class-connector-two-factor.php index 025e332e7..3510e18fd 100644 --- a/connectors/class-connector-two-factor.php +++ b/connectors/class-connector-two-factor.php @@ -1,7 +1,10 @@ esc_html_x( 'User Settings', 'two-factor', 'stream' ), - 'auth' => esc_html_x( 'Authentications', 'two-factor', 'stream' ), + 'auth' => esc_html_x( 'Authenticated', 'two-factor', 'stream' ), ); } @@ -101,7 +103,7 @@ public function get_context_labels() { public function register() { parent::register(); - add_filter( 'wp_stream_log_data', [ $this, 'log_override' ] ); + add_filter( 'wp_stream_log_data', array( $this, 'log_override' ) ); } /** @@ -122,7 +124,6 @@ public function log_override( $data ) { 'sessions' === $data['context'] && 'login' === $data['action'] && \Two_Factor_Core::is_user_using_two_factor( $data['user_id'] ) && - 'set_logged_in_cookie' === current_filter() && has_filter( 'send_auth_cookies', '__return_false' ) ) { $data = false; @@ -153,7 +154,7 @@ public function callback_two_factor_user_authenticated( $user, $provider ) { /** * Callback to watch for failed logins with Two Factor errors. * - * @param string $user_login User login. + * @param string $user_login User login. * @param \WP_Error $error WP_Error object. */ public function callback_wp_login_failed( $user_login, $error ) { @@ -192,7 +193,7 @@ public function callback_update_user_meta( $meta_id, $user_id, $meta_key, $new_m unset( $meta_id ); unset( $new_meta_value ); - switch( $meta_key ) { + switch ( $meta_key ) { case '_two_factor_backup_codes': case '_two_factor_totp_key': case '_two_factor_enabled_providers': @@ -215,7 +216,7 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ $old_meta_value = $this->user_meta[ $user_id ][ $meta_key ] ?? null; unset( $this->user_meta[ $user_id ][ $meta_key ] ); - switch( $meta_key ) { + switch ( $meta_key ) { case '_two_factor_backup_codes': $this->log( esc_html__( 'Updated backup codes', 'stream' ), @@ -235,8 +236,8 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ ); break; case '_two_factor_enabled_providers': - $old_providers = $old_meta_value ?? []; - $new_providers = $new_meta_value ?? []; + $old_providers = $old_meta_value ?? array(); + $new_providers = $new_meta_value ?? array(); $enabled_providers = array_diff( $new_providers, $old_providers ); $disabled_providers = array_diff( $old_providers, $new_providers ); @@ -277,10 +278,10 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ * @param string $meta_key Meta key. * @param mixed $meta_value Meta value. */ - public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_value ) { + public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_value ) { unset( $meta_id ); - switch( $meta_key ) { + switch ( $meta_key ) { case '_two_factor_backup_codes': $this->log( esc_html__( 'Added backup codes', 'stream' ), From 3c3ab3b9f7ff49f30b246969e26d494e69d146c2 Mon Sep 17 00:00:00 2001 From: JJ Date: Fri, 23 Aug 2024 12:08:01 -0400 Subject: [PATCH 05/10] add tests, update log messages --- connectors/class-connector-two-factor.php | 57 +++++-- connectors/class-connector-users.php | 2 +- phpunit-multisite.xml | 2 +- phpunit.xml | 2 +- .../test-class-connector-two-factor.php | 156 ++++++++++++++++++ 5 files changed, 203 insertions(+), 16 deletions(-) create mode 100644 tests/phpunit/connectors/test-class-connector-two-factor.php diff --git a/connectors/class-connector-two-factor.php b/connectors/class-connector-two-factor.php index 3510e18fd..f22dee133 100644 --- a/connectors/class-connector-two-factor.php +++ b/connectors/class-connector-two-factor.php @@ -139,8 +139,15 @@ public function log_override( $data ) { * @param object $provider The 2FA Provider used. */ public function callback_two_factor_user_authenticated( $user, $provider ) { - $this->log( + + /* Translators: %s is the Two Factor provider. */ + $message = __( 'Authenticated via %s', + 'stream' + ); + + $this->log( + $message, array( 'provider' => $provider->get_key(), ), @@ -167,10 +174,16 @@ public function callback_wp_login_failed( $user_login, $error ) { $user = get_user_by( 'email', $user_login ); } + /* Translators: %1$s is the user display name, %2$s is the error code, %3$s is the error message. */ + $message = __( + '%1$s Failed 2FA: %2$s %3$s', + 'stream' + ); + $this->log( - '%s Failed 2FA: %s %s', + $message, array( - 'display_name' => $user->display_name, + 'display_name' => $this->escape_percentages( $user->display_name ), 'code' => $error->get_error_code(), 'error' => $error->get_error_message(), ), @@ -219,7 +232,7 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ switch ( $meta_key ) { case '_two_factor_backup_codes': $this->log( - esc_html__( 'Updated backup codes', 'stream' ), + __( 'Updated backup codes', 'stream' ), array(), $user_id, 'user-settings', @@ -228,7 +241,7 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ break; case '_two_factor_totp_key': $this->log( - esc_html__( 'Set TOTP secret key' ), + __( 'Set TOTP secret key', 'stream' ), array(), $user_id, 'user-settings', @@ -243,9 +256,15 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ $disabled_providers = array_diff( $old_providers, $new_providers ); foreach ( $enabled_providers as $provider ) { + + /* Translators: %s is the Two Factor provider. */ + $message = __( + 'Enabled provider: %s', + 'stream' + ); + $this->log( - /* Translators: %s is the provider */ - esc_html__( 'Enabled provider: %s', 'stream' ), + $message, array( 'provider' => $provider, ), @@ -256,9 +275,15 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ } foreach ( $disabled_providers as $provider ) { + + /* Translators: %s is the Two Factor provider. */ + $message = __( + 'Disabled provider: %s', + 'stream' + ); + $this->log( - /* Translators: %s is the provider */ - esc_html__( 'Disabled provider: %s', 'stream' ), + $message, array( 'provider' => $provider, ), @@ -284,7 +309,7 @@ public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_v switch ( $meta_key ) { case '_two_factor_backup_codes': $this->log( - esc_html__( 'Added backup codes', 'stream' ), + __( 'Added backup codes', 'stream' ), array(), $user_id, 'user-settings', @@ -293,7 +318,7 @@ public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_v break; case '_two_factor_totp_key': $this->log( - esc_html__( 'Added TOTP secret key' ), + __( 'Added TOTP secret key', 'stream' ), array(), $user_id, 'user-settings', @@ -302,9 +327,15 @@ public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_v break; case '_two_factor_enabled_providers': foreach ( $meta_value as $provider ) { + + /* Translators: %s is the Two Factor provider. */ + $message = __( + 'Enabled provider: %s', + 'stream' + ); + $this->log( - /* Translators: %s is the provider */ - esc_html__( 'Enabled provider: %s', 'stream' ), + $message, array( 'provider' => $provider, ), diff --git a/connectors/class-connector-users.php b/connectors/class-connector-users.php index 6eb24357f..91b6287e7 100644 --- a/connectors/class-connector-users.php +++ b/connectors/class-connector-users.php @@ -182,7 +182,7 @@ public function callback_user_register( $user_id ) { * @param int $user_id Registered user ID. * @param \WP_User $user Registered user object. */ - public function callback_profile_update( $user_id, $user, $new_userdata ) { + public function callback_profile_update( $user_id, $user ) { unset( $user_id ); $this->log( diff --git a/phpunit-multisite.xml b/phpunit-multisite.xml index d3107e843..6169376bc 100644 --- a/phpunit-multisite.xml +++ b/phpunit-multisite.xml @@ -27,7 +27,7 @@ - + diff --git a/phpunit.xml b/phpunit.xml index f02a303d9..990e46c67 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -27,7 +27,7 @@ - + diff --git a/tests/phpunit/connectors/test-class-connector-two-factor.php b/tests/phpunit/connectors/test-class-connector-two-factor.php new file mode 100644 index 000000000..da06ae25f --- /dev/null +++ b/tests/phpunit/connectors/test-class-connector-two-factor.php @@ -0,0 +1,156 @@ +plugin->connectors->unload_connectors(); + + // Make partial of Connector_Two_Factor class, with mocked "log" function. + $this->mock = $this->getMockBuilder( Connector_Two_Factor::class ) + ->onlyMethods( array( 'log' ) ) + ->getMock(); + + // Register connector. + $this->mock->register(); + + // Allow us to have the Two_Factor_Dummy option. + remove_all_filters( 'two_factor_providers' ); + + if ( empty( $this->user_id ) ) { + $this->user_id = self::factory()->user->create( + array( + 'user_login' => 'testuser', + 'user_role' => 'administrator', + 'display_name' => 'testuserdisplay', + ) + ); + + $this->user = get_user_by( 'ID', $this->user_id ); + + \Two_Factor_Core::enable_provider_for_user( $this->user_id, 'Two_Factor_Dummy' ); + } + } + + /** + * Confirm that Two Factor is installed and active. + */ + public function test_two_factor_installed_and_activated() { + $this->assertTrue( class_exists( 'Two_Factor_Core' ) ); + } + + /** + * Test that adding a provider triggers the log. + */ + public function test_callback_added_user_meta() { + + $this->mock->expects( $this->once() ) + ->method( 'log' ) + ->with( + $this->equalTo( + __( + 'Enabled provider: %s', + 'stream' + ) + ), + $this->equalTo( + array( + 'provider' => 'Two_Factor_Email', + ) + ), + $this->user_id, + 'user-settings', + 'enabled' + ); + + \Two_Factor_Core::enable_provider_for_user( $this->user_id, 'Two_Factor_Email' ); + } + + /** + * Tests the "callback_save_two_factor_user_authenticated" callback. + * This tests the log via doing the action. + */ + public function test_callback_two_factor_user_authenticated() { + + wp_set_current_user( $this->user_id ); + + $this->mock->expects( $this->once() ) + ->method( 'log' ) + ->with( + $this->equalTo( + __( + 'Authenticated via %s', + 'stream' + ) + ), + $this->equalTo( + array( + 'provider' => 'Two_Factor_Dummy', + ) + ), + $this->user_id, + 'auth', + 'authenticated', + $this->user_id + ); + + $provider = \Two_Factor_Core::get_provider_for_user( $this->user, 'Two_Factor_Dummy' ); + + // We can't test the method so we'll trigger the action. + do_action( 'two_factor_user_authenticated', $this->user, \Two_Factor_Core::get_provider_for_user( $this->user, $provider ) ); + } + + /** + * Test that adding a provider triggers the log. + */ + public function test_callback_updated_user_meta() { + + $this->mock->expects( $this->once() ) + ->method( 'log' ) + ->with( + $this->equalTo( + __( + 'Disabled provider: %s', + 'stream' + ) + ), + $this->equalTo( + array( + 'provider' => 'Two_Factor_Dummy', + ), + ), + $this->user_id, + 'user-settings', + 'disabled' + ); + + \Two_Factor_Core::disable_provider_for_user( $this->user_id, 'Two_Factor_Dummy' ); + } +} From ba083a01e6a117b3bbb7ae077d0b2bde95f4e7fc Mon Sep 17 00:00:00 2001 From: JJ Date: Fri, 23 Aug 2024 12:11:44 -0400 Subject: [PATCH 06/10] updated connectors.md for completeness --- connectors.md | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/connectors.md b/connectors.md index 9b128c884..989d38642 100644 --- a/connectors.md +++ b/connectors.md @@ -832,6 +832,31 @@ +## Connector: WP_Stream\Connector_Two_Factor + +### Actions + + - update_user_meta + - updated_user_meta + - added_user_meta + - two_factor_user_authenticated + - wp_login_failed + +### Class register() + +
+This is the register method for the Connector. Occasionally there are additional actions in here. + +```php + public function register() { + parent::register(); + + add_filter( 'wp_stream_log_data', array( $this, 'log_override' ) ); + } +``` +
+ + ## Connector: WP_Stream\Connector_User_Switching ### Actions From 50e834ef8d0195a9ed8377734c479a3cb7dff9eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Krzemin=CC=81ski?= Date: Tue, 8 Oct 2024 14:18:48 +0200 Subject: [PATCH 07/10] chore: update class docs --- tests/phpunit/connectors/test-class-connector-two-factor.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/phpunit/connectors/test-class-connector-two-factor.php b/tests/phpunit/connectors/test-class-connector-two-factor.php index da06ae25f..b167baee7 100644 --- a/tests/phpunit/connectors/test-class-connector-two-factor.php +++ b/tests/phpunit/connectors/test-class-connector-two-factor.php @@ -1,8 +1,8 @@ Date: Tue, 8 Oct 2024 17:03:19 +0200 Subject: [PATCH 08/10] Resolve conflicts --- composer.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.lock b/composer.lock index c27b19915..c612755b2 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "8238a49e6491e7a1369be939e9e43b83", + "content-hash": "81ba0f890407d548fa7dcf88b79a1d3f", "packages": [ { "name": "composer/installers", @@ -8724,5 +8724,5 @@ "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } From c80d6002330cd7e76f6e94bfffa2ffac090840d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Krzemin=CC=81ski?= Date: Tue, 8 Oct 2024 17:21:02 +0200 Subject: [PATCH 09/10] chore: reorder methods --- connectors/class-connector-two-factor.php | 125 +++++++++++----------- 1 file changed, 63 insertions(+), 62 deletions(-) diff --git a/connectors/class-connector-two-factor.php b/connectors/class-connector-two-factor.php index f22dee133..30b3c4ba8 100644 --- a/connectors/class-connector-two-factor.php +++ b/connectors/class-connector-two-factor.php @@ -132,68 +132,6 @@ public function log_override( $data ) { return $data; } - /** - * Callback to watch for 2FA authenticated actions. - * - * @param \WP_User $user Authenticated user. - * @param object $provider The 2FA Provider used. - */ - public function callback_two_factor_user_authenticated( $user, $provider ) { - - /* Translators: %s is the Two Factor provider. */ - $message = __( - 'Authenticated via %s', - 'stream' - ); - - $this->log( - $message, - array( - 'provider' => $provider->get_key(), - ), - $user->ID, - 'auth', - 'authenticated', - $user->ID - ); - } - - /** - * Callback to watch for failed logins with Two Factor errors. - * - * @param string $user_login User login. - * @param \WP_Error $error WP_Error object. - */ - public function callback_wp_login_failed( $user_login, $error ) { - if ( ! str_starts_with( $error->get_error_code(), 'two_factor_' ) ) { - return; - } - - $user = get_user_by( 'login', $user_login ); - if ( ! $user && is_email( $user_login ) ) { - $user = get_user_by( 'email', $user_login ); - } - - /* Translators: %1$s is the user display name, %2$s is the error code, %3$s is the error message. */ - $message = __( - '%1$s Failed 2FA: %2$s %3$s', - 'stream' - ); - - $this->log( - $message, - array( - 'display_name' => $this->escape_percentages( $user->display_name ), - 'code' => $error->get_error_code(), - 'error' => $error->get_error_message(), - ), - $user->ID, - 'auth', - 'failed', - $user->ID - ); - } - /** * Callback to watch for user_meta changes BEFORE it's made. * @@ -295,6 +233,7 @@ public function callback_updated_user_meta( $meta_id, $user_id, $meta_key, $new_ break; } } + /** * Callback to watch for user_meta changes AFTER it's added. * @@ -347,4 +286,66 @@ public function callback_added_user_meta( $meta_id, $user_id, $meta_key, $meta_v break; } } + + /** + * Callback to watch for 2FA authenticated actions. + * + * @param \WP_User $user Authenticated user. + * @param object $provider The 2FA Provider used. + */ + public function callback_two_factor_user_authenticated( $user, $provider ) { + + /* Translators: %s is the Two Factor provider. */ + $message = __( + 'Authenticated via %s', + 'stream' + ); + + $this->log( + $message, + array( + 'provider' => $provider->get_key(), + ), + $user->ID, + 'auth', + 'authenticated', + $user->ID + ); + } + + /** + * Callback to watch for failed logins with Two Factor errors. + * + * @param string $user_login User login. + * @param \WP_Error $error WP_Error object. + */ + public function callback_wp_login_failed( $user_login, $error ) { + if ( ! str_starts_with( $error->get_error_code(), 'two_factor_' ) ) { + return; + } + + $user = get_user_by( 'login', $user_login ); + if ( ! $user && is_email( $user_login ) ) { + $user = get_user_by( 'email', $user_login ); + } + + /* Translators: %1$s is the user display name, %2$s is the error code, %3$s is the error message. */ + $message = __( + '%1$s Failed 2FA: %2$s %3$s', + 'stream' + ); + + $this->log( + $message, + array( + 'display_name' => $this->escape_percentages( $user->display_name ), + 'code' => $error->get_error_code(), + 'error' => $error->get_error_message(), + ), + $user->ID, + 'auth', + 'failed', + $user->ID + ); + } } From 3bb555d114168b35fef5a3ac3ba79941c2c2edca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcin=20Krzemin=CC=81ski?= Date: Tue, 8 Oct 2024 17:23:09 +0200 Subject: [PATCH 10/10] chore: fix typo --- connectors/class-connector-two-factor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/connectors/class-connector-two-factor.php b/connectors/class-connector-two-factor.php index 30b3c4ba8..2cf1155b7 100644 --- a/connectors/class-connector-two-factor.php +++ b/connectors/class-connector-two-factor.php @@ -28,7 +28,7 @@ class Connector_Two_Factor extends Connector { 'updated_user_meta', // After user meta changes. 'added_user_meta', // After user meta is added. - 'two_factor_user_authenticated', // Authenticatd via 2FA. + 'two_factor_user_authenticated', // Authenticate via 2FA. 'wp_login_failed', // Failed login. );