Skip to content

Commit 5f02d99

Browse files
committed
Fix handling of users with multiple identities
ROS users were identified by the pair (identity, auth_url), and in the v10 port this was translated to (user_id, provider_type), but that isn't actually how Atlas users work. An App is the equivalent of the old auth_url, and within an App user_id uniquely identifies a user. Users don't actually have a "provider type" at all: users have one or more identities, each of which has a provider type, and the same SyncUser should be used regardless of which identity was used to log in. This had surprisingly mild consequences, but was visibly broken in a few ways. Logging into the same user using two different identities resulted in two SyncUsers with the same user id. Opening the same partition (or any flx Realm) with both users resulted in both using whichever SyncUser happened to open it first, and the second would not create a SyncSession. This meant that most of the potential bad things (such as creating two session for one file) didn't actually happen, but the second user's list of sessions would be "incorrect", and removing one of the users would remove the incorrect set of local files. Linking identities to anonymous users resulted in the user still being treated as anonymous. The primary negative effect of this was that linking an identity, logging out, then logging back in would result in the user being removed entirely in between, forcing the re-download of all Realms (and the loss of any un-uploaded data). This bumps the schema version of the metadata Realm primarily for the sake of recovery on metadata Realms in invalid states: the migration block handles the case of multiple users with the same user id and unifies them. Since this needs a migration anyway, it fixes some incidental problems with the metadata Realm's schema which weren't fixable without a migration: the marked_for_deletion column was redundant with the Removed state, identity objects were leaked due to not being embedded, and the local_uuid field is no longer used for anything other than opening files written by early v10 versions, so there's no need to populate it for new users.
1 parent f486ffa commit 5f02d99

29 files changed

+959
-937
lines changed

CHANGELOG.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,21 @@
55

66
### Fixed
77
* <How do the end-user experience this issue? what was the impact?> ([#????](https://github.com/realm/realm-core/issues/????), since v?.?.?)
8-
* None.
8+
* Logging into a single user using multiple auth providers created a separate SyncUser per auth provider. This mostly worked, but had some quirks:
9+
- Sync sessions would not necessarily be associated with the specific SyncUser used to create them. As a result, querying a user for its sessions could give incorrect results, and logging one user out could close the wrong sessions.
10+
- Existing local synchronized Realm files created using version of Realm from August - November 2020 would sometimes not be opened correctly and would instead be redownloaded.
11+
- Removing one of the SyncUsers would delete all local Realm files for all SyncUsers for that user.
12+
- Deleting the server-side user via one of the SyncUsers left the other SyncUsers in an invalid state.
13+
- A SyncUser which was originally created via anonymous login and then linked to an identity would still be treated as an anonymous users and removed entirely on logout.
14+
(since v10.0.0)
915

1016
### Breaking changes
11-
* None.
17+
* SyncUser::provider_type() and realm_user_get_auth_provider() have been removed. Users don't have provider types; identities do.
18+
* SyncUser no longer has a `local_identity()`. `identity()` has been guaranteed to be unique per App ever since v10.
1219

1320
### Compatibility
1421
* Fileformat: Generates files with format v23. Reads and automatically upgrade from fileformat v5.
22+
* The metadata Realm used to store sync users has had its schema version bumped. It is automatically migrated to the new version on first open. Downgrading to older version of Realm after upgrading will discard stored user tokens and require logging back in.
1523

1624
-----------
1725

src/realm.h

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3232,13 +3232,9 @@ RLM_API realm_user_state_e realm_user_get_state(const realm_user_t* user) RLM_AP
32323232
RLM_API bool realm_user_get_all_identities(const realm_user_t* user, realm_user_identity_t* out_identities,
32333233
size_t capacity, size_t* out_n);
32343234

3235-
RLM_API const char* realm_user_get_local_identity(const realm_user_t*) RLM_API_NOEXCEPT;
3236-
32373235
// returned pointer must be manually released with realm_free()
32383236
RLM_API char* realm_user_get_device_id(const realm_user_t*) RLM_API_NOEXCEPT;
32393237

3240-
RLM_API realm_auth_provider_e realm_user_get_auth_provider(const realm_user_t*) RLM_API_NOEXCEPT;
3241-
32423238
/**
32433239
* Log out the user and mark it as logged out.
32443240
*

src/realm/collection.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ struct CollectionIterator;
1919
/// Collections are bound to particular properties of an object. In a
2020
/// collection's public interface, the implementation must take care to keep the
2121
/// object consistent with the persisted state, mindful of the fact that the
22-
/// state may have changed as a consquence of modifications from other instances
22+
/// state may have changed as a consequence of modifications from other instances
2323
/// referencing the same persisted state.
2424
class CollectionBase {
2525
public:

src/realm/object-store/c_api/app.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -642,11 +642,6 @@ RLM_API bool realm_user_get_all_identities(const realm_user_t* user, realm_user_
642642
});
643643
}
644644

645-
RLM_API const char* realm_user_get_local_identity(const realm_user_t* user) noexcept
646-
{
647-
return (*user)->local_identity().c_str();
648-
}
649-
650645
RLM_API char* realm_user_get_device_id(const realm_user_t* user) noexcept
651646
{
652647
if ((*user)->has_device_id()) {
@@ -656,11 +651,6 @@ RLM_API char* realm_user_get_device_id(const realm_user_t* user) noexcept
656651
return nullptr;
657652
}
658653

659-
RLM_API realm_auth_provider_e realm_user_get_auth_provider(const realm_user_t* user) noexcept
660-
{
661-
return realm_auth_provider_e(enum_from_provider_type((*user)->provider_type()));
662-
}
663-
664654
RLM_API bool realm_user_log_out(realm_user_t* user)
665655
{
666656
return wrap_err([&] {

src/realm/object-store/sync/app.cpp

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ void App::log_in_with_credentials(
644644
// is already an anonymous session active, reuse it
645645
if (credentials.provider() == AuthProvider::ANONYMOUS) {
646646
for (auto&& user : m_sync_manager->all_users()) {
647-
if (user->provider_type() == credentials.provider_as_string() && user->is_logged_in()) {
647+
if (user->is_anonymous()) {
648648
completion(switch_user(user), util::none);
649649
return;
650650
}
@@ -686,8 +686,7 @@ void App::log_in_with_credentials(
686686
else {
687687
sync_user = self->m_sync_manager->get_user(
688688
get<std::string>(json, "user_id"), get<std::string>(json, "refresh_token"),
689-
get<std::string>(json, "access_token"), credentials.provider_as_string(),
690-
get<std::string>(json, "device_id"));
689+
get<std::string>(json, "access_token"), get<std::string>(json, "device_id"));
691690
}
692691
}
693692
catch (const AppError& e) {
@@ -758,11 +757,7 @@ std::shared_ptr<SyncUser> App::switch_user(const std::shared_ptr<SyncUser>& user
758757
if (!user || user->state() != SyncUser::State::LoggedIn) {
759758
throw AppError(ErrorCodes::ClientUserNotLoggedIn, "User is no longer valid or is logged out");
760759
}
761-
762-
auto users = m_sync_manager->all_users();
763-
auto it = std::find(users.begin(), users.end(), user);
764-
765-
if (it == users.end()) {
760+
if (!verify_user_present(user)) {
766761
throw AppError(ErrorCodes::ClientUserNotFound, "User does not exist");
767762
}
768763

src/realm/object-store/sync/impl/sync_file.cpp

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -295,11 +295,11 @@ bool SyncFileManager::copy_realm_file(const std::string& old_path, const std::st
295295
return true;
296296
}
297297

298-
bool SyncFileManager::remove_realm(const std::string& user_identity, const std::string& local_identity,
298+
bool SyncFileManager::remove_realm(const std::string& user_identity,
299+
const std::vector<std::string>& legacy_user_identities,
299300
const std::string& raw_realm_path, const std::string& partition) const
300301
{
301-
util::Optional<std::string> existing =
302-
get_existing_realm_file_path(user_identity, local_identity, raw_realm_path, partition);
302+
auto existing = get_existing_realm_file_path(user_identity, legacy_user_identities, raw_realm_path, partition);
303303
if (existing) {
304304
return remove_realm(*existing);
305305
}
@@ -327,10 +327,10 @@ static bool try_file_remove(const std::string& path) noexcept
327327
}
328328
}
329329

330-
util::Optional<std::string> SyncFileManager::get_existing_realm_file_path(const std::string& user_identity,
331-
const std::string& local_user_identity,
332-
const std::string& realm_file_name,
333-
const std::string& partition) const
330+
util::Optional<std::string>
331+
SyncFileManager::get_existing_realm_file_path(const std::string& user_identity,
332+
const std::vector<std::string>& legacy_user_identities,
333+
const std::string& realm_file_name, const std::string& partition) const
334334
{
335335
std::string preferred_name = preferred_realm_path_without_suffix(user_identity, realm_file_name);
336336
if (try_file_exists(preferred_name)) {
@@ -365,14 +365,14 @@ util::Optional<std::string> SyncFileManager::get_existing_realm_file_path(const
365365
}
366366
}
367367

368-
if (!local_user_identity.empty()) {
368+
for (auto& legacy_identity : legacy_user_identities) {
369369
// retain support for legacy paths
370-
std::string old_path = legacy_realm_file_path(local_user_identity, realm_file_name);
370+
std::string old_path = legacy_realm_file_path(legacy_identity, realm_file_name);
371371
if (try_file_exists(old_path)) {
372372
return old_path;
373373
}
374374
// retain support for legacy local identity paths
375-
std::string old_local_identity_path = legacy_local_identity_path(local_user_identity, partition);
375+
std::string old_local_identity_path = legacy_local_identity_path(legacy_identity, partition);
376376
if (try_file_exists(old_local_identity_path)) {
377377
return old_local_identity_path;
378378
}
@@ -381,11 +381,12 @@ util::Optional<std::string> SyncFileManager::get_existing_realm_file_path(const
381381
return util::none;
382382
}
383383

384-
std::string SyncFileManager::realm_file_path(const std::string& user_identity, const std::string& local_user_identity,
384+
std::string SyncFileManager::realm_file_path(const std::string& user_identity,
385+
const std::vector<std::string>& legacy_user_identities,
385386
const std::string& realm_file_name, const std::string& partition) const
386387
{
387-
util::Optional<std::string> existing_path =
388-
get_existing_realm_file_path(user_identity, local_user_identity, realm_file_name, partition);
388+
auto existing_path =
389+
get_existing_realm_file_path(user_identity, legacy_user_identities, realm_file_name, partition);
389390
if (existing_path) {
390391
return *existing_path;
391392
}

src/realm/object-store/sync/impl/sync_file.hpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,15 +69,16 @@ class SyncFileManager {
6969
static bool try_file_exists(const std::string& path) noexcept;
7070

7171
util::Optional<std::string> get_existing_realm_file_path(const std::string& user_identity,
72-
const std::string& local_user_identity,
72+
const std::vector<std::string>& legacy_user_identities,
7373
const std::string& realm_file_name,
7474
const std::string& partition) const;
7575
/// Return the path for a given Realm, creating the user directory if it does not already exist.
76-
std::string realm_file_path(const std::string& user_identity, const std::string& local_user_identity,
76+
std::string realm_file_path(const std::string& user_identity,
77+
const std::vector<std::string>& legacy_user_identities,
7778
const std::string& realm_file_name, const std::string& partition) const;
7879

7980
/// Remove the Realm at a given path for a given user. Returns `true` if the remove operation fully succeeds.
80-
bool remove_realm(const std::string& user_identity, const std::string& local_identity,
81+
bool remove_realm(const std::string& user_identity, const std::vector<std::string>& legacy_user_identities,
8182
const std::string& realm_file_name, const std::string& partition) const;
8283

8384
/// Remove the Realm whose primary Realm file is located at `absolute_path`. Returns `true` if the remove

0 commit comments

Comments
 (0)