Repository navigation
feat: tell a second browser from a second tab with a per-room session hash #815
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
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
eca5932
feat: record which login session wrote each presence row
josephfusco 77afecf
feat: return the session hash from REST and the personal data export
josephfusco e46c03f
test: cover the session hash across tabs, browsers, rooms and REST
josephfusco 979740b
docs: document session_hash on entries
josephfusco ab5b264
test: cover the browser row in the personal data export
josephfusco 103715c
fix: move the room version when a tab signs in again
josephfusco 182336b
test: check the token never reaches the row or the REST response
josephfusco f9bf712
Merge branch 'main' into feat/presence-session
josephfusco File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,170 @@ | ||
| <?php | ||
| /** | ||
| * Tests for the session hash that tells a second browser from a second tab. | ||
| * | ||
| * @package Presence_API | ||
| * | ||
| * @group presence | ||
| */ | ||
| class WP_Test_Presence_Session_Hash extends WP_Presence_UnitTestCase { | ||
|
|
||
| /** | ||
| * Editor who writes the rows. | ||
| * | ||
| * @var int | ||
| */ | ||
| protected static $editor_id; | ||
|
|
||
| public static function wpSetUpBeforeClass( WP_UnitTest_Factory $factory ) { | ||
| self::$editor_id = $factory->user->create( array( 'role' => 'editor' ) ); | ||
| } | ||
|
|
||
| public function tear_down() { | ||
| unset( $_COOKIE[ LOGGED_IN_COOKIE ] ); | ||
| parent::tear_down(); | ||
| } | ||
|
|
||
| /** | ||
| * Signs the editor in with a fresh login session, as a browser of their own would. | ||
| * | ||
| * @return string The session token. | ||
| */ | ||
| private function sign_in() { | ||
| $expiration = time() + DAY_IN_SECONDS; | ||
| $token = WP_Session_Tokens::get_instance( self::$editor_id )->create( $expiration ); | ||
|
|
||
| $_COOKIE[ LOGGED_IN_COOKIE ] = wp_generate_auth_cookie( self::$editor_id, $expiration, 'logged_in', $token ); | ||
| wp_set_current_user( self::$editor_id ); | ||
|
|
||
| return $token; | ||
| } | ||
|
|
||
| /** | ||
| * Returns the session_hash stored for a client. | ||
| * | ||
| * @param string $room Room. | ||
| * @param string $client_id Client ID. | ||
| * @return string The stored value. | ||
| */ | ||
| private function stored_hash( $room, $client_id ) { | ||
| foreach ( wp_get_presence( $room ) as $entry ) { | ||
| if ( $entry->client_id === $client_id ) { | ||
| return $entry->session_hash; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| /** | ||
| * Two tabs of one login share a value and a second login gets another, and no token reaches the table. | ||
| * | ||
| * @covers ::wp_presence_session_hash | ||
| * @covers ::wp_set_presence | ||
| */ | ||
| public function test_tabs_share_a_session_and_browsers_do_not() { | ||
| global $wpdb; | ||
|
|
||
| $room = 'postType/post:1'; | ||
|
|
||
| $laptop = $this->sign_in(); | ||
| wp_set_presence( $room, 'tab-1', array(), array( 'user_id' => self::$editor_id ) ); | ||
| wp_set_presence( $room, 'tab-2', array(), array( 'user_id' => self::$editor_id ) ); | ||
|
|
||
| $phone = $this->sign_in(); | ||
| wp_set_presence( $room, 'tab-3', array(), array( 'user_id' => self::$editor_id ) ); | ||
|
|
||
| $first = $this->stored_hash( $room, 'tab-1' ); | ||
| $this->assertMatchesRegularExpression( '/^[0-9a-f]{16}$/', $first ); | ||
| $this->assertSame( $first, $this->stored_hash( $room, 'tab-2' ) ); | ||
| $this->assertNotSame( $first, $this->stored_hash( $room, 'tab-3' ) ); | ||
|
|
||
| // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching | ||
| $rows = wp_json_encode( $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->presence} WHERE room = %s", $room ) ) ); | ||
| $this->assertStringNotContainsString( $laptop, $rows ); | ||
| $this->assertStringNotContainsString( $phone, $rows ); | ||
| } | ||
|
|
||
| /** | ||
| * Signing in again in the same tab changes what peers see, so it moves the room version like any other change. | ||
| * | ||
| * @covers ::wp_presence_write_row | ||
| */ | ||
| public function test_a_new_login_in_the_same_tab_moves_the_version() { | ||
| $room = 'postType/post:1'; | ||
|
|
||
| $this->sign_in(); | ||
| wp_set_presence( $room, 'tab-1', array(), array( 'user_id' => self::$editor_id ) ); | ||
| $before = wp_get_presence_room_versions( $room )[ $room ]; | ||
|
|
||
| $this->sign_in(); | ||
| wp_set_presence( $room, 'tab-1', array(), array( 'user_id' => self::$editor_id ) ); | ||
|
|
||
| $this->assertSame( (string) ( $before + 1 ), wp_get_presence_room_versions( $room )[ $room ] ); | ||
| } | ||
|
|
||
| /** | ||
| * One session hashes differently per room, so the value cannot follow it from room to room. | ||
| * | ||
| * @covers ::wp_presence_session_hash | ||
| */ | ||
| public function test_one_session_differs_per_room() { | ||
| $this->sign_in(); | ||
|
|
||
| $this->assertNotSame( wp_presence_session_hash( 'postType/post:1' ), wp_presence_session_hash( 'postType/post:2' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * A write without the user's own session, or for another user, records no browser. | ||
| * | ||
| * @covers ::wp_set_presence | ||
| */ | ||
| public function test_writes_without_the_users_session_stay_empty() { | ||
| $room = 'postType/post:1'; | ||
|
|
||
| wp_set_current_user( self::$editor_id ); | ||
| wp_set_presence( $room, 'cli-1', array(), array( 'user_id' => self::$editor_id ) ); | ||
| $this->assertSame( '', $this->stored_hash( $room, 'cli-1' ) ); | ||
|
|
||
| $this->sign_in(); | ||
| wp_set_presence( $room, 'agent-1', array(), array( 'user_id' => self::factory()->user->create() ) ); | ||
| $this->assertSame( '', $this->stored_hash( $room, 'agent-1' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * The personal data export names the browser a row came from, and leaves it out for a row with none. | ||
| * | ||
| * @covers ::wp_presence_personal_data_exporter | ||
| */ | ||
| public function test_the_export_includes_the_browser() { | ||
| $room = 'postType/post:1'; | ||
| $this->sign_in(); | ||
| wp_set_presence( $room, 'tab-1', array(), array( 'user_id' => self::$editor_id ) ); | ||
|
|
||
| $export = wp_presence_personal_data_exporter( get_userdata( self::$editor_id )->user_email ); | ||
| $this->assertContains( wp_presence_session_hash( $room ), wp_list_pluck( $export['data'][0]['data'], 'value' ) ); | ||
|
|
||
| unset( $_COOKIE[ LOGGED_IN_COOKIE ] ); | ||
| wp_set_presence( $room, 'tab-1', array(), array( 'user_id' => self::$editor_id ) ); | ||
|
|
||
| $export = wp_presence_personal_data_exporter( get_userdata( self::$editor_id )->user_email ); | ||
| $this->assertNotContains( 'Browser', wp_list_pluck( $export['data'][0]['data'], 'name' ) ); | ||
| } | ||
|
|
||
| /** | ||
| * The REST collection returns the hash a browser wrote, and never the token behind it. | ||
| * | ||
| * @covers WP_REST_Presence_Controller::prepare_item_for_response | ||
| */ | ||
| public function test_rest_returns_the_hash() { | ||
| $room = 'postType/post:' . self::factory()->post->create( array( 'post_author' => self::$editor_id ) ); | ||
| $token = $this->sign_in(); | ||
| wp_set_presence( $room, 'tab-1', array(), array( 'user_id' => self::$editor_id ) ); | ||
|
|
||
| $request = new WP_REST_Request( 'GET', '/wp-presence/v1/presence' ); | ||
| $request->set_param( 'room', $room ); | ||
| $data = rest_get_server()->dispatch( $request )->get_data(); | ||
|
|
||
| $this->assertSame( wp_presence_session_hash( $room ), $data[0]['session_hash'] ); | ||
| $this->assertStringNotContainsString( $token, wp_json_encode( $data ) ); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.