From 61f4b1befdf0ace7d3612dc1a72f178512d16ea8 Mon Sep 17 00:00:00 2001 From: James Stone Date: Wed, 6 Oct 2021 18:27:26 -0700 Subject: [PATCH 1/2] Fix user tokens not being threadsafe This could manifest in a segfault if the access token refresh handler was triggered at a specific time while the user is logged out from a different thread. Specifically, resetting the refresh jwt at the same time that it was being copied out by `SyncUser::refresh_jwt()` and the util::Optional was read as being valid but it was just set to util::none, and now the thread requesting the jwt triggers a copy of invalid random data. --- CHANGELOG.md | 1 + src/realm/object-store/sync/sync_session.cpp | 9 ++--- src/realm/object-store/sync/sync_user.cpp | 18 ++++++++-- src/realm/object-store/sync/sync_user.hpp | 8 ++--- test/object-store/sync/user.cpp | 36 ++++++++++++++++++++ 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a8236980abc..f6f459af765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * Crash when quering with 'Not()' followed by empty group. ([#4168](https://github.com/realm/realm-core/issues/4168) since v1.0.0) * Change Apple/Linux temp dir created with `util::make_temp_dir()` to default to the environment's TMPDIR if available. Make `TestFile` clean up the directories it creates when finished. ([#4921](https://github.com/realm/realm-core/issues/4921)) * Fixed a rare assertion failure or deadlock when a sync session is racing to close at the same time that external reference to the Realm is being released. ([#4931](https://github.com/realm/realm-core/issues/4931)) +* Fixed a rare segfault which could trigger if a user was being logged out while the access token refresh response comes in. ([#4944](https://github.com/realm/realm-core/issues/4944), since v10) ### Breaking changes * `App::Config::transport_factory` was replaced with `App::Config::transport`. It should now be an instance of `GenericNetworkTransport` rather than a factory for making instances. This allows the SDK to control which thread constructs the transport layer. ([#4903](https://github.com/realm/realm-core/pull/4903)) diff --git a/src/realm/object-store/sync/sync_session.cpp b/src/realm/object-store/sync/sync_session.cpp index 8c4f9e1a90e..b9eb86cc9d6 100644 --- a/src/realm/object-store/sync/sync_session.cpp +++ b/src/realm/object-store/sync/sync_session.cpp @@ -361,18 +361,13 @@ const SyncSession::State& SyncSession::State::waiting_for_access_token = Waiting std::function)> SyncSession::handle_refresh(std::shared_ptr session) { return [session](util::Optional error) { - using namespace std::chrono; - auto session_user = session->user(); - auto is_user_expired = - session_user && session_user->refresh_jwt().expires_at < - duration_cast(system_clock::now().time_since_epoch()).count(); if (!session_user) { std::unique_lock lock(session->m_state_mutex); session->cancel_pending_waits(lock, error ? error->error_code : std::error_code()); } - else if (is_user_expired) { + else if (session_user->refresh_token_is_expired()) { // user is expired std::unique_lock lock(session->m_state_mutex); session->cancel_pending_waits(lock, error ? error->error_code : std::error_code()); if (session->m_config.error_handler) { @@ -402,7 +397,7 @@ std::function)> SyncSession::handle_refresh(s } else { // 10 seconds is arbitrary, but it is to not swamp the server - std::this_thread::sleep_for(milliseconds(10000)); + std::this_thread::sleep_for(std::chrono::seconds(10)); if (session_user) { session_user->refresh_custom_data(handle_refresh(session)); } diff --git a/src/realm/object-store/sync/sync_user.cpp b/src/realm/object-store/sync/sync_user.cpp index f113e37b65a..613443cb4a7 100644 --- a/src/realm/object-store/sync/sync_user.cpp +++ b/src/realm/object-store/sync/sync_user.cpp @@ -362,7 +362,13 @@ void SyncUser::log_out() bool SyncUser::is_logged_in() const { - std::lock_guard lock(m_mutex); + std::unique_lock lock(m_mutex); + return do_is_logged_in(lock); +} + +bool SyncUser::do_is_logged_in(std::unique_lock& lock) const +{ + REALM_ASSERT(lock.owns_lock()); return !m_access_token.token.empty() && !m_refresh_token.token.empty() && m_state == State::LoggedIn; } @@ -495,8 +501,16 @@ bool SyncUser::access_token_refresh_required() const { using namespace std::chrono; constexpr size_t buffer_seconds = 5; // arbitrary + std::unique_lock lock(m_mutex); auto threshold = duration_cast(system_clock::now().time_since_epoch()).count() - buffer_seconds; - return is_logged_in() && m_access_token.expires_at < static_cast(threshold); + return do_is_logged_in(lock) && m_access_token.expires_at < static_cast(threshold); +} + +bool SyncUser::refresh_token_is_expired() const +{ + using namespace std::chrono; + std::lock_guard guard(m_mutex); + return m_refresh_token.expires_at < duration_cast(system_clock::now().time_since_epoch()).count(); } } // namespace realm diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index 2711d216a26..f2e4bf2160b 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -261,11 +261,6 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba std::string refresh_token() const; - RealmJWT refresh_jwt() const - { - return m_refresh_token; - } - std::string device_id() const; bool has_device_id() const; @@ -297,6 +292,7 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns /// true. bool access_token_refresh_required() const; + bool refresh_token_is_expired() const; // Optionally set a context factory. If so, must be set before any sessions are created. static void set_binding_context_factory(SyncUserContextFactory factory); @@ -315,6 +311,8 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba static SyncUserContextFactory s_binding_context_factory; static std::mutex s_binding_context_factory_mutex; + bool do_is_logged_in(std::unique_lock& lock) const; + State m_state; util::AtomicSharedPtr m_binding_context; diff --git a/test/object-store/sync/user.cpp b/test/object-store/sync/user.cpp index 03e7b244022..4ddfb13c4a7 100644 --- a/test/object-store/sync/user.cpp +++ b/test/object-store/sync/user.cpp @@ -168,6 +168,42 @@ TEST_CASE("sync_user: logout", "[sync]") { } } +TEST_CASE("sync_user: token thread safety", "[sync]") { + TestSyncManager init_sync_manager(TestSyncManager::Config(base_path, SyncManager::MetadataMode::NoMetadata)); + auto sync_manager = init_sync_manager.app()->sync_manager(); + const std::string identity = "sync_test_identity"; + const std::string refresh_token = realm::encode_fake_jwt("1234567890-fake-refresh-token"); + const std::string access_token = realm::encode_fake_jwt("1234567890-fake-access-token"); + const std::string server_url = "https://realm.example.org"; + auto user = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); + + using namespace std::chrono_literals; + std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); + int64_t valid_time = std::chrono::system_clock::to_time_t(now + 30min); + int64_t expired_time = std::chrono::system_clock::to_time_t(now - 30min); + + SECTION("stress test") { + constexpr size_t num_iterations = 1000; + auto shared_code = [&]() { + for (size_t i = 0; i < num_iterations; ++i) { + bool should_refresh = user->access_token_refresh_required(); + int64_t flipped = should_refresh ? valid_time : expired_time; + std::string access_token = realm::encode_fake_jwt("1234567890-fake-access-token", flipped, flipped); + user->update_access_token(std::move(access_token)); + should_refresh = user->refresh_token_is_expired(); + flipped = should_refresh ? valid_time : expired_time; + std::string refresh_token = realm::encode_fake_jwt("1234567890-fake-refresh-token", flipped, flipped); + user->update_refresh_token(std::move(refresh_token)); + } + }; + JoiningThread worker([&] { + shared_code(); + }); + shared_code(); + worker.join(); + } +} + TEST_CASE("sync_user: user persistence", "[sync]") { TestSyncManager init_sync_manager( TestSyncManager::Config("baz_app_id", base_path, SyncManager::MetadataMode::NoEncryption)); From bf07d22387dd48010beab20fd1c2e90e8bb456c7 Mon Sep 17 00:00:00 2001 From: James Stone Date: Thu, 7 Oct 2021 17:24:13 -0700 Subject: [PATCH 2/2] use static checking for protected SyncUser members --- src/realm/object-store/sync/sync_user.cpp | 72 +++++++++++------------ src/realm/object-store/sync/sync_user.hpp | 71 +++++++++++----------- test/object-store/sync/user.cpp | 36 ------------ 3 files changed, 72 insertions(+), 107 deletions(-) diff --git a/src/realm/object-store/sync/sync_user.cpp b/src/realm/object-store/sync/sync_user.cpp index 613443cb4a7..169c7980bfd 100644 --- a/src/realm/object-store/sync/sync_user.cpp +++ b/src/realm/object-store/sync/sync_user.cpp @@ -88,8 +88,8 @@ SyncUser::SyncUser(std::string refresh_token, const std::string identity, const SyncManager* sync_manager) : m_state(state) , m_provider_type(provider_type) - , m_refresh_token(RealmJWT(std::move(refresh_token))) , m_identity(std::move(identity)) + , m_refresh_token(RealmJWT(std::move(refresh_token))) , m_access_token(RealmJWT(std::move(access_token))) , m_device_id(device_id) , m_sync_manager(sync_manager) @@ -102,6 +102,7 @@ SyncUser::SyncUser(std::string refresh_token, const std::string identity, const } bool updated = m_sync_manager->perform_metadata_update([&](const auto& manager) { + util::CheckedLockGuard lock(m_mutex); auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); metadata->set_state_and_tokens(m_state, m_access_token.token, m_refresh_token.token); metadata->set_device_id(m_device_id); @@ -116,7 +117,7 @@ SyncUser::~SyncUser() {} std::shared_ptr SyncUser::sync_manager() const { - std::lock_guard lk(m_mutex); + util::CheckedLockGuard lk(m_mutex); REALM_ASSERT(m_sync_manager); REALM_ASSERT(m_state != SyncUser::State::Removed); return m_sync_manager->shared_from_this(); @@ -124,7 +125,7 @@ std::shared_ptr SyncUser::sync_manager() const void SyncUser::detach_from_sync_manager() { - std::lock_guard lk(m_mutex); + util::CheckedLockGuard lk(m_mutex); REALM_ASSERT(m_sync_manager); m_state = SyncUser::State::Removed; m_sync_manager = nullptr; @@ -132,7 +133,7 @@ void SyncUser::detach_from_sync_manager() std::vector> SyncUser::all_sessions() { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); std::vector> sessions; if (m_state == State::Removed) { return sessions; @@ -151,7 +152,7 @@ std::vector> SyncUser::all_sessions() std::shared_ptr SyncUser::session_for_on_disk_path(const std::string& path) { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); if (m_state == State::Removed) { return nullptr; } @@ -172,7 +173,7 @@ void SyncUser::update_state_and_tokens(SyncUser::State state, const std::string& { std::vector> sessions_to_revive; { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); m_state = state; m_access_token = access_token.empty() ? RealmJWT{} : RealmJWT(access_token); m_refresh_token = refresh_token.empty() ? RealmJWT{} : RealmJWT(refresh_token); @@ -197,9 +198,9 @@ void SyncUser::update_state_and_tokens(SyncUser::State state, const std::string& } } - m_sync_manager->perform_metadata_update([&](const auto& manager) { + m_sync_manager->perform_metadata_update([&, state = m_state](const auto& manager) { auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); - metadata->set_state_and_tokens(m_state, access_token, refresh_token); + metadata->set_state_and_tokens(state, access_token, refresh_token); }); } // (Re)activate all pending sessions. @@ -216,7 +217,7 @@ void SyncUser::update_refresh_token(std::string&& token) { std::vector> sessions_to_revive; { - std::unique_lock lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); switch (m_state) { case State::Removed: return; @@ -238,9 +239,9 @@ void SyncUser::update_refresh_token(std::string&& token) } } - m_sync_manager->perform_metadata_update([&](const auto& manager) { + m_sync_manager->perform_metadata_update([&, raw_refresh_token = m_refresh_token.token](const auto& manager) { auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); - metadata->set_refresh_token(m_refresh_token.token); + metadata->set_refresh_token(raw_refresh_token); }); } // (Re)activate all pending sessions. @@ -257,7 +258,7 @@ void SyncUser::update_access_token(std::string&& token) { std::vector> sessions_to_revive; { - std::unique_lock lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); switch (m_state) { case State::Removed: return; @@ -279,9 +280,9 @@ void SyncUser::update_access_token(std::string&& token) } } - m_sync_manager->perform_metadata_update([&](const auto& manager) { + m_sync_manager->perform_metadata_update([&, raw_access_token = m_access_token.token](const auto& manager) { auto metadata = manager.get_or_make_user_metadata(m_identity, m_provider_type); - metadata->set_access_token(m_access_token.token); + metadata->set_access_token(raw_access_token); }); } @@ -297,14 +298,14 @@ void SyncUser::update_access_token(std::string&& token) std::vector SyncUser::identities() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return m_user_identities; } void SyncUser::update_identities(std::vector identities) { - std::unique_lock lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); REALM_ASSERT(m_state == SyncUser::State::LoggedIn); m_user_identities = identities; @@ -320,7 +321,7 @@ void SyncUser::log_out() // after we've been marked as logged out. std::shared_ptr sync_manager_shared; { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); if (m_state != State::LoggedIn) { return; } @@ -362,13 +363,12 @@ void SyncUser::log_out() bool SyncUser::is_logged_in() const { - std::unique_lock lock(m_mutex); - return do_is_logged_in(lock); + util::CheckedLockGuard lock(m_mutex); + return do_is_logged_in(); } -bool SyncUser::do_is_logged_in(std::unique_lock& lock) const +bool SyncUser::do_is_logged_in() const { - REALM_ASSERT(lock.owns_lock()); return !m_access_token.token.empty() && !m_refresh_token.token.empty() && m_state == State::LoggedIn; } @@ -379,37 +379,37 @@ void SyncUser::invalidate() std::string SyncUser::refresh_token() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return m_refresh_token.token; } std::string SyncUser::access_token() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return m_access_token.token; } std::string SyncUser::device_id() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return m_device_id; } bool SyncUser::has_device_id() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return !m_device_id.empty() && m_device_id != "000000000000000000000000"; } SyncUser::State SyncUser::state() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return m_state; } void SyncUser::set_state(SyncUser::State state) { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); m_state = state; REALM_ASSERT(m_sync_manager); @@ -421,19 +421,19 @@ void SyncUser::set_state(SyncUser::State state) SyncUserProfile SyncUser::user_profile() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return m_user_profile; } util::Optional SyncUser::custom_data() const { - std::lock_guard lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); return m_access_token.user_data; } void SyncUser::update_user_profile(const SyncUserProfile& profile) { - std::unique_lock lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); REALM_ASSERT(m_state == SyncUser::State::LoggedIn); m_user_profile = profile; @@ -447,7 +447,7 @@ void SyncUser::update_user_profile(const SyncUserProfile& profile) void SyncUser::register_session(std::shared_ptr session) { const std::string& path = session->path(); - std::unique_lock lock(m_mutex); + util::CheckedUniqueLock lock(m_mutex); switch (m_state) { case State::LoggedIn: // Immediately ask the session to come online. @@ -465,7 +465,7 @@ void SyncUser::register_session(std::shared_ptr session) app::MongoClient SyncUser::mongo_client(const std::string& service_name) { - std::lock_guard lk(m_mutex); + util::CheckedLockGuard lk(m_mutex); REALM_ASSERT(m_state == SyncUser::State::LoggedIn); return app::MongoClient(shared_from_this(), m_sync_manager->app().lock(), service_name); } @@ -479,7 +479,7 @@ void SyncUser::set_binding_context_factory(SyncUserContextFactory factory) void SyncUser::refresh_custom_data(std::function)> completion_block) { auto app = [&]() -> std::shared_ptr { - std::lock_guard lk(m_mutex); + util::CheckedLockGuard lk(m_mutex); if (!m_sync_manager || m_state == SyncUser::State::Removed) { return nullptr; } @@ -501,15 +501,15 @@ bool SyncUser::access_token_refresh_required() const { using namespace std::chrono; constexpr size_t buffer_seconds = 5; // arbitrary - std::unique_lock lock(m_mutex); + util::CheckedLockGuard lock(m_mutex); auto threshold = duration_cast(system_clock::now().time_since_epoch()).count() - buffer_seconds; - return do_is_logged_in(lock) && m_access_token.expires_at < static_cast(threshold); + return do_is_logged_in() && m_access_token.expires_at < static_cast(threshold); } bool SyncUser::refresh_token_is_expired() const { using namespace std::chrono; - std::lock_guard guard(m_mutex); + util::CheckedLockGuard guard(m_mutex); return m_refresh_token.expires_at < duration_cast(system_clock::now().time_since_epoch()).count(); } diff --git a/src/realm/object-store/sync/sync_user.hpp b/src/realm/object-store/sync/sync_user.hpp index f2e4bf2160b..bb9961732e6 100644 --- a/src/realm/object-store/sync/sync_user.hpp +++ b/src/realm/object-store/sync/sync_user.hpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include @@ -206,13 +207,13 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba ~SyncUser(); // Return a list of all sessions belonging to this user. - std::vector> all_sessions(); + std::vector> all_sessions() REQUIRES(!m_mutex); // Return a session for a given on disk path. // In most cases, bindings shouldn't expose this to consumers, since the on-disk // path for a synced Realm is an opaque implementation detail. This API is retained // for testing purposes, and for bindings for consumers that are servers or tools. - std::shared_ptr session_for_on_disk_path(const std::string& path); + std::shared_ptr session_for_on_disk_path(const std::string& path) REQUIRES(!m_mutex); // Update the user's state and refresh/access tokens atomically in a Realm transaction. // If the user is transitioning between LoggedIn and LoggedOut, then the access_token and @@ -220,27 +221,27 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba // logged out and logged in. // Note that this is called by the SyncManager, and should not be directly called. void update_state_and_tokens(SyncUser::State state, const std::string& access_token, - const std::string& refresh_token); + const std::string& refresh_token) REQUIRES(!m_mutex); // Update the user's refresh token. If the user is logged out, it will log itself back in. // Note that this is called by the SyncManager, and should not be directly called. - void update_refresh_token(std::string&& token); + void update_refresh_token(std::string&& token) REQUIRES(!m_mutex); // Update the user's access token. If the user is logged out, it will log itself back in. // Note that this is called by the SyncManager, and should not be directly called. - void update_access_token(std::string&& token); + void update_access_token(std::string&& token) REQUIRES(!m_mutex); // Update the user's profile. - void update_user_profile(const SyncUserProfile& profile); + void update_user_profile(const SyncUserProfile& profile) REQUIRES(!m_mutex); // Update the user's identities. - void update_identities(std::vector identities); + void update_identities(std::vector identities) REQUIRES(!m_mutex); // Log the user out and mark it as such. This will also close its associated Sessions. - void log_out(); + void log_out() REQUIRES(!m_mutex); /// Returns true id the users access_token and refresh_token are set. - bool is_logged_in() const; + bool is_logged_in() const REQUIRES(!m_mutex); const std::string& identity() const noexcept { @@ -257,23 +258,23 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba return m_local_identity; } - std::string access_token() const; + std::string access_token() const REQUIRES(!m_mutex); - std::string refresh_token() const; + std::string refresh_token() const REQUIRES(!m_mutex); - std::string device_id() const; + std::string device_id() const REQUIRES(!m_mutex); - bool has_device_id() const; + bool has_device_id() const REQUIRES(!m_mutex); - SyncUserProfile user_profile() const; + SyncUserProfile user_profile() const REQUIRES(!m_mutex); - std::vector identities() const; + std::vector identities() const REQUIRES(!m_mutex); // Custom user data embedded in the access token. - util::Optional custom_data() const; + util::Optional custom_data() const REQUIRES(!m_mutex); - State state() const; - void set_state(SyncUser::State state); + State state() const REQUIRES(!m_mutex); + void set_state(SyncUser::State state) REQUIRES(!m_mutex); std::shared_ptr binding_context() const { @@ -284,36 +285,36 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba // A registered session will be bound at the earliest opportunity: either // immediately, or upon the user becoming Active. // Note that this is called by the SyncManager, and should not be directly called. - void register_session(std::shared_ptr); + void register_session(std::shared_ptr) REQUIRES(!m_mutex); /// Refreshes the custom data for this user - void refresh_custom_data(std::function)> completion_block); + void refresh_custom_data(std::function)> completion_block) REQUIRES(!m_mutex); /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns /// true. - bool access_token_refresh_required() const; - bool refresh_token_is_expired() const; + bool access_token_refresh_required() const REQUIRES(!m_mutex); + bool refresh_token_is_expired() const REQUIRES(!m_mutex); // Optionally set a context factory. If so, must be set before any sessions are created. static void set_binding_context_factory(SyncUserContextFactory factory); - std::shared_ptr sync_manager() const; + std::shared_ptr sync_manager() const REQUIRES(!m_mutex); /// Retrieves a general-purpose service client for the Realm Cloud service /// @param service_name The name of the cluster - app::MongoClient mongo_client(const std::string& service_name); + app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex); protected: friend class SyncManager; - void detach_from_sync_manager(); + void detach_from_sync_manager() REQUIRES(!m_mutex); private: static SyncUserContextFactory s_binding_context_factory; static std::mutex s_binding_context_factory_mutex; - bool do_is_logged_in(std::unique_lock& lock) const; + bool do_is_logged_in() const REQUIRES(m_mutex); - State m_state; + State m_state GUARDED_BY(m_mutex); util::AtomicSharedPtr m_binding_context; @@ -324,12 +325,9 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba const std::string m_provider_type; // Mark the user as invalid, since a fatal user-related error was encountered. - void invalidate(); + void invalidate() REQUIRES(!m_mutex); - mutable std::mutex m_mutex; - - // The user's refresh token. - RealmJWT m_refresh_token; + mutable util::CheckedMutex m_mutex; // Set by the server. The unique ID of the user account on the Realm Applcication. const std::string m_identity; @@ -341,13 +339,16 @@ class SyncUser : public std::enable_shared_from_this, public Subscriba // Waiting sessions are those that should be asked to connect once this user is logged in. std::unordered_map> m_waiting_sessions; + // The user's refresh token. + RealmJWT m_refresh_token GUARDED_BY(m_mutex); + // The user's access token. - RealmJWT m_access_token; + RealmJWT m_access_token GUARDED_BY(m_mutex); // The identities associated with this user. - std::vector m_user_identities; + std::vector m_user_identities GUARDED_BY(m_mutex); - SyncUserProfile m_user_profile; + SyncUserProfile m_user_profile GUARDED_BY(m_mutex); const std::string m_device_id; diff --git a/test/object-store/sync/user.cpp b/test/object-store/sync/user.cpp index 4ddfb13c4a7..03e7b244022 100644 --- a/test/object-store/sync/user.cpp +++ b/test/object-store/sync/user.cpp @@ -168,42 +168,6 @@ TEST_CASE("sync_user: logout", "[sync]") { } } -TEST_CASE("sync_user: token thread safety", "[sync]") { - TestSyncManager init_sync_manager(TestSyncManager::Config(base_path, SyncManager::MetadataMode::NoMetadata)); - auto sync_manager = init_sync_manager.app()->sync_manager(); - const std::string identity = "sync_test_identity"; - const std::string refresh_token = realm::encode_fake_jwt("1234567890-fake-refresh-token"); - const std::string access_token = realm::encode_fake_jwt("1234567890-fake-access-token"); - const std::string server_url = "https://realm.example.org"; - auto user = sync_manager->get_user(identity, refresh_token, access_token, server_url, dummy_device_id); - - using namespace std::chrono_literals; - std::chrono::system_clock::time_point now = std::chrono::system_clock::now(); - int64_t valid_time = std::chrono::system_clock::to_time_t(now + 30min); - int64_t expired_time = std::chrono::system_clock::to_time_t(now - 30min); - - SECTION("stress test") { - constexpr size_t num_iterations = 1000; - auto shared_code = [&]() { - for (size_t i = 0; i < num_iterations; ++i) { - bool should_refresh = user->access_token_refresh_required(); - int64_t flipped = should_refresh ? valid_time : expired_time; - std::string access_token = realm::encode_fake_jwt("1234567890-fake-access-token", flipped, flipped); - user->update_access_token(std::move(access_token)); - should_refresh = user->refresh_token_is_expired(); - flipped = should_refresh ? valid_time : expired_time; - std::string refresh_token = realm::encode_fake_jwt("1234567890-fake-refresh-token", flipped, flipped); - user->update_refresh_token(std::move(refresh_token)); - } - }; - JoiningThread worker([&] { - shared_code(); - }); - shared_code(); - worker.join(); - } -} - TEST_CASE("sync_user: user persistence", "[sync]") { TestSyncManager init_sync_manager( TestSyncManager::Config("baz_app_id", base_path, SyncManager::MetadataMode::NoEncryption));