diff --git a/includes/Core/Util/Migration_1_0_0.php b/includes/Core/Util/Migration_1_0_0.php new file mode 100644 index 00000000000..8a1c59c6e10 --- /dev/null +++ b/includes/Core/Util/Migration_1_0_0.php @@ -0,0 +1,130 @@ +context = $context; + $this->options = new Options( $context ); + } + + /** + * Registers hooks. + * + * @since 1.0.0 + */ + public function register() { + add_action( 'admin_init', array( $this, 'migrate' ) ); + } + + /** + * Migrates the DB. + * + * @since 1.0.0 + */ + public function migrate() { + $db_version = $this->options->get( 'googlesitekit_db_version' ); + + if ( ! $db_version || version_compare( $db_version, self::DB_VERSION, '<' ) ) { + $this->migrate_install(); + + $this->options->set( 'googlesitekit_db_version', self::DB_VERSION ); + } + } + + /** + * Migrates old credentials and disconnects users. + * + * @since 1.0.0 + */ + private function migrate_install() { + $credentials = ( new Encrypted_Options( $this->options ) )->get( Credentials::OPTION ); + + // Credentials can be filtered in so we must also check if there is a saved option present. + if ( isset( $credentials['oauth2_client_id'] ) && strpos( $credentials['oauth2_client_id'], '.apps.sitekit.withgoogle.com' ) ) { + $this->options->delete( Credentials::OPTION ); + $this->options->set( Beta_Migration::OPTION_IS_PRE_PROXY_INSTALL, 1 ); + + $this->disconnect_users(); + + wp_cache_flush(); + } + } + + /** + * Disconnects authenticated users. + * + * @since 1.0.0 + */ + private function disconnect_users() { + global $wpdb; + + $user_options = new User_Options( $this->context ); + $authentication = new Authentication( $this->context, $this->options, $user_options ); + + // User option keys are prefixed in single site and multisite when not in network mode. + $key_prefix = $this->context->is_network_mode() ? '' : $wpdb->get_blog_prefix(); + $user_ids = ( new \WP_User_Query( + array( + 'fields' => 'id', + 'meta_key' => $key_prefix . OAuth_Client::OPTION_ACCESS_TOKEN, + 'compare' => 'EXISTS', + ) + ) )->get_results(); + + foreach ( $user_ids as $user_id ) { + $user_options->switch_user( (int) $user_id ); + $authentication->disconnect(); + } + } +} diff --git a/includes/Plugin.php b/includes/Plugin.php index 814808abd6f..c6e22b0e3fe 100644 --- a/includes/Plugin.php +++ b/includes/Plugin.php @@ -148,6 +148,7 @@ function( $username, $user ) use ( $user_options ) { ( new Core\Util\Activation( $this->context, $options, $assets ) )->register(); ( new Core\Util\Beta_Migration( $this->context ) )->register(); + ( new Core\Util\Migration_1_0_0( $this->context ) )->register(); ( new Core\Util\Uninstallation( $reset ) )->register(); if ( defined( 'WP_DEBUG' ) && true === WP_DEBUG ) { diff --git a/tests/phpunit/integration/Core/Util/Migration_1_0_0Test.php b/tests/phpunit/integration/Core/Util/Migration_1_0_0Test.php new file mode 100644 index 00000000000..bb987d67acf --- /dev/null +++ b/tests/phpunit/integration/Core/Util/Migration_1_0_0Test.php @@ -0,0 +1,139 @@ +register(); + + $this->assertTrue( has_action( 'admin_init' ) ); + } + + public function test_migrate() { + $context = new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ); + $options = new Options( $context ); + $credentials = new Credentials( $options ); + $migration = new Migration_1_0_0( $context ); + + // Upgrade will update the DB version if run. + $this->delete_db_version(); + + $migration->migrate(); + + $this->assertEquals( Migration_1_0_0::DB_VERSION, $this->get_db_version() ); + + // The upgrade will NOT delete old GCP credentials if present. + $this->delete_db_version(); + $this->set_gcp_credentials(); + $this->assertTrue( $credentials->has() ); + + $migration->migrate(); + + $this->assertTrue( $credentials->has() ); + $this->assertEquals( Migration_1_0_0::DB_VERSION, $this->get_db_version() ); + + // The upgrade WILL delete proxy credentials if present. + $this->delete_db_version(); + $this->set_proxy_credentials(); + $this->assertTrue( $credentials->has() ); + + $migration->migrate(); + + $this->assertFalse( $credentials->has() ); + $this->assertEquals( Migration_1_0_0::DB_VERSION, $this->get_db_version() ); + + // The upgrade will not disconnect any user if GCP credentials are present. + $this->delete_db_version(); + $this->set_gcp_credentials(); + + $users_with_tokens = array( + $this->create_user_with_access_token(), + $this->create_user_with_access_token(), + $this->create_user_with_access_token(), + ); + $users_without = array( + $this->factory()->user->create(), + $this->factory()->user->create(), + $this->factory()->user->create(), + ); + + $migration->migrate(); + + foreach ( $users_with_tokens as $user_with_token ) { + $this->assertUserHasAccessToken( $user_with_token ); + } + + // The upgrade will disconnect any user with an auth token if proxy credentials are present. + $this->delete_db_version(); + $this->set_proxy_credentials(); + + $migration->migrate(); + + foreach ( $users_with_tokens as $user_who_had_token ) { + $this->assertUserNotHasAccessToken( $user_who_had_token ); + } + foreach ( $users_without as $user_without ) { + $this->assertUserNotHasAccessToken( $user_without ); + } + } + + private function assertUserHasAccessToken( $user_id ) { + $this->assertNotEmpty( get_user_option( OAuth_Client::OPTION_ACCESS_TOKEN, $user_id ) ); + } + + private function assertUserNotHasAccessToken( $user_id ) { + $this->assertEmpty( get_user_option( OAuth_Client::OPTION_ACCESS_TOKEN, $user_id ) ); + } + + private function create_user_with_access_token() { + $user_id = $this->factory()->user->create(); + update_user_option( $user_id, OAuth_Client::OPTION_ACCESS_TOKEN, "test-access-token-$user_id" ); + + return $user_id; + } + + private function get_db_version() { + return ( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) )->get( 'googlesitekit_db_version' ); + } + + private function delete_db_version() { + ( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) )->delete( 'googlesitekit_db_version' ); + } + + private function delete_credentials() { + ( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) )->delete( Credentials::OPTION ); + } + + private function set_gcp_credentials() { + ( new Credentials( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) ) )->set( array( + 'oauth2_client_id' => 'test-client-id.apps.googleusercontent.com', + 'oauth2_client_secret' => 'test-client-secret', + ) ); + } + + private function set_proxy_credentials() { + ( new Credentials( new Options( new Context( GOOGLESITEKIT_PLUGIN_MAIN_FILE ) ) ) )->set( array( + 'oauth2_client_id' => 'test-site-id.apps.sitekit.withgoogle.com', + 'oauth2_client_secret' => 'test-site-secret', + ) ); + } +}