• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 2029

12 Feb 2024 06:33PM UTC coverage: 91.838%. First build
2029

push

Evergreen

web-flow
Merge pull request #7329 from realm/tg/jwt-validation

Assorted sync metadata storage refactoring

93050 of 171486 branches covered (0.0%)

219 of 246 new or added lines in 23 files covered. (89.02%)

235273 of 256184 relevant lines covered (91.84%)

6516157.5 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

97.6
/src/realm/object-store/sync/impl/sync_metadata.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2016 Realm Inc.
4
//
5
// Licensed under the Apache License, Version 2.0 (the "License");
6
// you may not use this file except in compliance with the License.
7
// You may obtain a copy of the License at
8
//
9
// http://www.apache.org/licenses/LICENSE-2.0
10
//
11
// Unless required by applicable law or agreed to in writing, software
12
// distributed under the License is distributed on an "AS IS" BASIS,
13
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
// See the License for the specific language governing permissions and
15
// limitations under the License.
16
//
17
////////////////////////////////////////////////////////////////////////////
18

19
#include <realm/object-store/sync/impl/sync_metadata.hpp>
20
#include <realm/object-store/impl/realm_coordinator.hpp>
21

22
#include <realm/object-store/object_schema.hpp>
23
#include <realm/object-store/object_store.hpp>
24
#include <realm/object-store/property.hpp>
25
#include <realm/object-store/results.hpp>
26
#include <realm/object-store/schema.hpp>
27
#include <realm/object-store/sync/impl/sync_file.hpp>
28
#include <realm/object-store/util/uuid.hpp>
29
#include <realm/object-store/util/scheduler.hpp>
30
#if REALM_PLATFORM_APPLE
31
#include <realm/object-store/impl/apple/keychain_helper.hpp>
32
#endif
33

34
#include <realm/db.hpp>
35
#include <realm/dictionary.hpp>
36
#include <realm/table.hpp>
37

38
using namespace realm;
39

40
namespace {
41
static const char* const c_sync_userMetadata = "UserMetadata";
42
static const char* const c_sync_identityMetadata = "UserIdentity";
43

44
static const char* const c_sync_current_user_identity = "current_user_identity";
45

46
/* User keys */
47
static const char* const c_sync_identity = "identity";
48
static const char* const c_sync_legacy_uuids = "legacy_uuids";
49
static const char* const c_sync_refresh_token = "refresh_token";
50
static const char* const c_sync_access_token = "access_token";
51
static const char* const c_sync_identities = "identities";
52
static const char* const c_sync_state = "state";
53
static const char* const c_sync_device_id = "device_id";
54
static const char* const c_sync_profile_data = "profile_data";
55
static const char* const c_sync_local_realm_paths = "local_realm_paths";
56

57
/* Identity keys */
58
static const char* const c_sync_user_id = "id";
59
static const char* const c_sync_provider_type = "provider_type";
60

61
static const char* const c_sync_fileActionMetadata = "FileActionMetadata";
62
static const char* const c_sync_original_name = "original_name";
63
static const char* const c_sync_new_name = "new_name";
64
static const char* const c_sync_action = "action";
65
static const char* const c_sync_partition = "url";
66

67
realm::Schema make_schema()
68
{
755✔
69
    using namespace realm;
755✔
70
    return Schema{
755✔
71
        {c_sync_identityMetadata,
755✔
72
         ObjectSchema::ObjectType::Embedded,
755✔
73
         {
755✔
74
             {c_sync_user_id, PropertyType::String},
755✔
75
             {c_sync_provider_type, PropertyType::String},
755✔
76
         }},
755✔
77
        {c_sync_userMetadata,
755✔
78
         {{c_sync_identity, PropertyType::String},
755✔
79
          {c_sync_legacy_uuids, PropertyType::String | PropertyType::Array},
755✔
80
          {c_sync_refresh_token, PropertyType::String | PropertyType::Nullable},
755✔
81
          {c_sync_access_token, PropertyType::String | PropertyType::Nullable},
755✔
82
          {c_sync_identities, PropertyType::Object | PropertyType::Array, c_sync_identityMetadata},
755✔
83
          {c_sync_state, PropertyType::Int},
755✔
84
          {c_sync_device_id, PropertyType::String},
755✔
85
          {c_sync_profile_data, PropertyType::String},
755✔
86
          {c_sync_local_realm_paths, PropertyType::Set | PropertyType::String}}},
755✔
87
        {c_sync_fileActionMetadata,
755✔
88
         {
755✔
89
             {c_sync_original_name, PropertyType::String, Property::IsPrimary{true}},
755✔
90
             {c_sync_new_name, PropertyType::String | PropertyType::Nullable},
755✔
91
             {c_sync_action, PropertyType::Int},
755✔
92
             {c_sync_partition, PropertyType::String},
755✔
93
             {c_sync_identity, PropertyType::String},
755✔
94
         }},
755✔
95
        {c_sync_current_user_identity,
755✔
96
         {
755✔
97
             {c_sync_current_user_identity, PropertyType::String},
755✔
98
         }},
755✔
99
    };
755✔
100
}
755✔
101

102
void migrate_to_v7(std::shared_ptr<Realm> old_realm, std::shared_ptr<Realm> realm)
103
{
6✔
104
    // Before schema version 7 there may have been multiple UserMetadata entries
3✔
105
    // for a single user_id with different provider types, so we need to merge
3✔
106
    // any duplicates together
3✔
107

3✔
108
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata);
6✔
109
    TableRef old_table = ObjectStore::table_for_object_type(old_realm->read_group(), c_sync_userMetadata);
6✔
110
    if (table->is_empty())
6✔
111
        return;
×
112
    REALM_ASSERT(table->size() == old_table->size());
6✔
113

3✔
114
    ColKey id_col = table->get_column_key(c_sync_identity);
6✔
115
    ColKey old_uuid_col = old_table->get_column_key("local_uuid");
6✔
116
    ColKey new_uuid_col = table->get_column_key(c_sync_legacy_uuids);
6✔
117
    ColKey state_col = table->get_column_key(c_sync_state);
6✔
118

3✔
119
    std::unordered_map<std::string, Obj> users;
6✔
120
    for (size_t i = 0, j = 0; i < table->size(); ++j) {
36✔
121
        auto obj = table->get_object(i);
30✔
122

15✔
123
        // Move the local uuid from the old column to the list
15✔
124
        auto old_obj = old_table->get_object(j);
30✔
125
        obj.get_list<String>(new_uuid_col).add(old_obj.get<String>(old_uuid_col));
30✔
126

15✔
127
        // Check if we've already seen an object with the same id. If not, store
15✔
128
        // this one and move on
15✔
129
        std::string user_id = obj.get<String>(id_col);
30✔
130
        auto& existing = users[obj.get<String>(id_col)];
30✔
131
        if (!existing.is_valid()) {
30✔
132
            existing = obj;
18✔
133
            ++i;
18✔
134
            continue;
18✔
135
        }
18✔
136

6✔
137
        // We have a second object for the same id, so we need to merge them.
6✔
138
        // First we merge the state: if one is logged in and the other isn't,
6✔
139
        // we'll use the logged-in state and tokens. If both are logged in, we'll
6✔
140
        // use the more recent login. If one is logged out and the other is
6✔
141
        // removed we'll use the logged out state. If both are logged out or
6✔
142
        // both are removed then it doesn't matter which we pick.
6✔
143
        using State = SyncUser::State;
12✔
144
        auto state = State(obj.get<int64_t>(state_col));
12✔
145
        auto existing_state = State(existing.get<int64_t>(state_col));
12✔
146
        if (state == existing_state) {
12✔
147
            if (state == State::LoggedIn) {
4✔
148
                RealmJWT token_1(existing.get<StringData>(c_sync_access_token));
4✔
149
                RealmJWT token_2(obj.get<StringData>(c_sync_access_token));
4✔
150
                if (token_1.issued_at < token_2.issued_at) {
4✔
151
                    existing.set(c_sync_refresh_token, obj.get<StringData>(c_sync_refresh_token));
2✔
152
                    existing.set(c_sync_access_token, obj.get<StringData>(c_sync_access_token));
2✔
153
                }
2✔
154
            }
4✔
155
        }
4✔
156
        else if (state == State::LoggedIn || existing_state == State::Removed) {
8✔
157
            existing.set(c_sync_state, int64_t(state));
4✔
158
            existing.set(c_sync_refresh_token, obj.get<StringData>(c_sync_refresh_token));
4✔
159
            existing.set(c_sync_access_token, obj.get<StringData>(c_sync_access_token));
4✔
160
        }
4✔
161

6✔
162
        // Next we merge the list properties (identities, legacy uuids, realm file paths)
6✔
163
        {
12✔
164
            auto dest = existing.get_linklist(c_sync_identities);
12✔
165
            auto src = obj.get_linklist(c_sync_identities);
12✔
166
            for (size_t i = 0, size = src.size(); i < size; ++i) {
36✔
167
                if (dest.find_first(src.get(i)) == npos) {
24✔
168
                    dest.add(src.get(i));
12✔
169
                }
12✔
170
            }
24✔
171
        }
12✔
172
        {
12✔
173
            auto dest = existing.get_list<String>(c_sync_legacy_uuids);
12✔
174
            auto src = obj.get_list<String>(c_sync_legacy_uuids);
12✔
175
            for (size_t i = 0, size = src.size(); i < size; ++i) {
24✔
176
                if (dest.find_first(src.get(i)) == npos) {
12✔
177
                    dest.add(src.get(i));
12✔
178
                }
12✔
179
            }
12✔
180
        }
12✔
181
        {
12✔
182
            auto dest = existing.get_set<String>(c_sync_local_realm_paths);
12✔
183
            auto src = obj.get_set<String>(c_sync_local_realm_paths);
12✔
184
            for (size_t i = 0, size = src.size(); i < size; ++i) {
36✔
185
                dest.insert(src.get(i));
24✔
186
            }
24✔
187
        }
12✔
188

6✔
189

6✔
190
        // Finally we delete the duplicate object. We don't increment `i` as it's
6✔
191
        // now the index of the object just after the one we're deleting.
6✔
192
        obj.remove();
12✔
193
    }
12✔
194
}
6✔
195

196
} // anonymous namespace
197

198
// MARK: - Sync metadata manager
199

200
SyncMetadataManager::SyncMetadataManager(std::string path, bool should_encrypt,
201
                                         util::Optional<std::vector<char>> encryption_key)
202
{
756✔
203
    constexpr uint64_t SCHEMA_VERSION = 7;
756✔
204

370✔
205
    if (!REALM_PLATFORM_APPLE && should_encrypt && !encryption_key)
756!
206
        throw InvalidArgument("Metadata Realm encryption was specified, but no encryption key was provided.");
1✔
207

369✔
208
    m_metadata_config.automatic_change_notifications = false;
755✔
209
    m_metadata_config.path = path;
755✔
210
    m_metadata_config.schema = make_schema();
755✔
211
    m_metadata_config.schema_version = SCHEMA_VERSION;
755✔
212
    m_metadata_config.schema_mode = SchemaMode::Automatic;
755✔
213
    m_metadata_config.scheduler = util::Scheduler::make_dummy();
755✔
214
    if (encryption_key)
755✔
215
        m_metadata_config.encryption_key = std::move(*encryption_key);
10✔
216
    m_metadata_config.automatically_handle_backlinks_in_migrations = true;
755✔
217
    m_metadata_config.migration_function = [](std::shared_ptr<Realm> old_realm, std::shared_ptr<Realm> realm,
755✔
218
                                              Schema&) {
372✔
219
        if (old_realm->schema_version() < 7) {
6✔
220
            migrate_to_v7(old_realm, realm);
6✔
221
        }
6✔
222
    };
6✔
223

369✔
224
    auto realm = open_realm(should_encrypt, encryption_key != none);
755✔
225

369✔
226
    // Get data about the (hardcoded) schemas
369✔
227
    auto object_schema = realm->schema().find(c_sync_userMetadata);
755✔
228
    m_user_schema = {
755✔
229
        object_schema->persisted_properties[0].column_key, object_schema->persisted_properties[1].column_key,
755✔
230
        object_schema->persisted_properties[2].column_key, object_schema->persisted_properties[3].column_key,
755✔
231
        object_schema->persisted_properties[4].column_key, object_schema->persisted_properties[5].column_key,
755✔
232
        object_schema->persisted_properties[6].column_key, object_schema->persisted_properties[7].column_key,
755✔
233
        object_schema->persisted_properties[8].column_key};
755✔
234

369✔
235
    object_schema = realm->schema().find(c_sync_fileActionMetadata);
755✔
236
    m_file_action_schema = {
755✔
237
        object_schema->persisted_properties[0].column_key, object_schema->persisted_properties[1].column_key,
755✔
238
        object_schema->persisted_properties[2].column_key, object_schema->persisted_properties[3].column_key,
755✔
239
        object_schema->persisted_properties[4].column_key,
755✔
240
    };
755✔
241
}
755✔
242

243
void SyncMetadataManager::perform_launch_actions(SyncFileManager& file_manager) const
244
{
663✔
245
    auto realm = get_realm();
663✔
246

323✔
247
    // Perform our "on next startup" actions such as deleting Realm files
323✔
248
    // which we couldn't delete immediately due to them being in use
323✔
249
    auto actions_table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata);
663✔
250
    for (auto file_action : *actions_table) {
348✔
251
        SyncFileActionMetadata md(m_file_action_schema, realm, file_action);
50✔
252
        run_file_action(file_manager, md);
50✔
253
    }
50✔
254

323✔
255
    // Delete any users marked for death.
323✔
256
    auto users_table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata);
663✔
257
    for (auto user : *users_table) {
342✔
258
        if (user.get<int64_t>(m_user_schema.state_col) != int64_t(SyncUser::State::Removed))
38✔
259
            continue;
34✔
260
        try {
4✔
261
            SyncUserMetadata data(m_user_schema, realm, user);
4✔
262
            file_manager.remove_user_realms(data.identity(), data.realm_file_paths());
4✔
263
            realm->begin_transaction();
4✔
264
            user.remove();
4✔
265
            realm->commit_transaction();
4✔
266
        }
4✔
267
        catch (FileAccessError const&) {
2✔
NEW
268
            continue;
×
NEW
269
        }
×
270
    }
4✔
271
}
663✔
272

273
bool SyncMetadataManager::run_file_action(SyncFileManager& file_manager, SyncFileActionMetadata& md) const
274
{
56✔
275
    switch (md.action()) {
56✔
276
        case SyncFileActionMetadata::Action::DeleteRealm:
14✔
277
            // Delete all the files for the given Realm.
7✔
278
            if (file_manager.remove_realm(md.original_name())) {
14✔
279
                md.remove();
14✔
280
                return true;
14✔
281
            }
14✔
NEW
282
            break;
×
283
        case SyncFileActionMetadata::Action::BackUpThenDeleteRealm:
42✔
284
            // Copy the primary Realm file to the recovery dir, and then delete the Realm.
21✔
285
            auto new_name = md.new_name();
42✔
286
            auto original_name = md.original_name();
42✔
287
            if (!util::File::exists(original_name)) {
42✔
288
                // The Realm file doesn't exist anymore.
9✔
289
                md.remove();
18✔
290
                return false;
18✔
291
            }
18✔
292
            if (new_name && !util::File::exists(*new_name) &&
24✔
293
                file_manager.copy_realm_file(original_name, *new_name)) {
23✔
294
                // We successfully copied the Realm file to the recovery directory.
11✔
295
                bool did_remove = file_manager.remove_realm(original_name);
22✔
296
                // if the copy succeeded but not the delete, then running BackupThenDelete
11✔
297
                // a second time would fail, so change this action to just delete the original file.
11✔
298
                if (did_remove) {
22✔
299
                    md.remove();
20✔
300
                    return true;
20✔
301
                }
20✔
302
                md.set_action(SyncFileActionMetadata::Action::DeleteRealm);
2✔
303
            }
2✔
304
            break;
14✔
305
    }
4✔
306
    return false;
4✔
307
}
4✔
308

309
// Some of our string columns are nullable. They never should actually be
310
// null as we store "" rather than null when the value isn't present, but
311
// be safe and handle it anyway.
312
static std::string_view get_string(const Obj& obj, ColKey col)
313
{
66✔
314
    auto str = obj.get<String>(col);
66✔
315
    return str.is_null() ? "" : std::string_view(str);
65✔
316
}
66✔
317

318
static bool is_valid_user(const SyncUserMetadata::Schema& schema, const Obj& obj)
319
{
34✔
320
    // This is overly cautious and merely checking the state should suffice,
17✔
321
    // but because this is a persisted file that can be modified it's possible
17✔
322
    // to get invalid combinations of data.
17✔
323
    return obj && obj.get<int64_t>(schema.state_col) == int64_t(SyncUser::State::LoggedIn) &&
34✔
324
           RealmJWT::validate(get_string(obj, schema.access_token_col)) &&
34✔
325
           RealmJWT::validate(get_string(obj, schema.refresh_token_col));
33✔
326
}
34✔
327

328
std::vector<SyncUserMetadata> SyncMetadataManager::all_logged_in_users() const
329
{
663✔
330
    auto realm = get_realm();
663✔
331
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata);
663✔
332
    std::vector<SyncUserMetadata> users;
663✔
333
    users.reserve(table->size());
663✔
334
    for (auto obj : *table) {
340✔
335
        if (is_valid_user(m_user_schema, obj)) {
34✔
336
            users.emplace_back(m_user_schema, realm, obj);
32✔
337
        }
32✔
338
    }
34✔
339
    return users;
663✔
340
}
663✔
341

342
SyncUserMetadataResults SyncMetadataManager::all_unmarked_users() const
343
{
22✔
344
    return get_users(false);
22✔
345
}
22✔
346

347
SyncUserMetadataResults SyncMetadataManager::all_users_marked_for_removal() const
348
{
10✔
349
    return get_users(true);
10✔
350
}
10✔
351

352
SyncUserMetadataResults SyncMetadataManager::get_users(bool marked) const
353
{
32✔
354
    auto realm = get_realm();
32✔
355
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata);
32✔
356
    Query query;
32✔
357
    if (marked) {
32✔
358
        query = table->where().equal(m_user_schema.state_col, int64_t(SyncUser::State::Removed));
10✔
359
    }
10✔
360
    else {
22✔
361
        query = table->where().not_equal(m_user_schema.state_col, int64_t(SyncUser::State::Removed));
22✔
362
    }
22✔
363
    return SyncUserMetadataResults(Results(realm, std::move(query)), m_user_schema);
32✔
364
}
32✔
365

366
util::Optional<std::string> SyncMetadataManager::get_current_user_identity() const
367
{
49✔
368
    auto realm = get_realm();
49✔
369
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_current_user_identity);
49✔
370

24✔
371
    if (!table->is_empty()) {
49✔
372
        auto first = table->begin();
47✔
373
        return util::Optional<std::string>(first->get<String>(c_sync_current_user_identity));
47✔
374
    }
47✔
375

1✔
376
    return util::Optional<std::string>();
2✔
377
}
2✔
378

379
SyncFileActionMetadataResults SyncMetadataManager::all_pending_actions() const
380
{
28✔
381
    auto realm = get_realm();
28✔
382
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata);
28✔
383
    return SyncFileActionMetadataResults(Results(realm, table), m_file_action_schema);
28✔
384
}
28✔
385

386
void SyncMetadataManager::set_current_user_identity(const std::string& identity)
387
{
1,120✔
388
    auto realm = get_realm();
1,120✔
389

549✔
390
    realm->begin_transaction();
1,120✔
391

549✔
392
    TableRef currentUserIdentityTable =
1,120✔
393
        ObjectStore::table_for_object_type(realm->read_group(), c_sync_current_user_identity);
1,120✔
394

549✔
395
    Obj currentUserIdentityObj;
1,120✔
396
    if (currentUserIdentityTable->is_empty())
1,120✔
397
        currentUserIdentityObj = currentUserIdentityTable->create_object();
×
398
    else
1,120✔
399
        currentUserIdentityObj = *currentUserIdentityTable->begin();
1,120✔
400

549✔
401
    currentUserIdentityObj.set<String>(c_sync_current_user_identity, identity);
1,120✔
402

549✔
403
    realm->commit_transaction();
1,120✔
404
}
1,120✔
405

406
util::Optional<SyncUserMetadata> SyncMetadataManager::get_or_make_user_metadata(const std::string& identity,
407
                                                                                bool make_if_absent) const
408
{
2,473✔
409
    auto realm = get_realm();
2,473✔
410
    auto& schema = m_user_schema;
2,473✔
411

1,213✔
412
    // Retrieve or create the row for this object.
1,213✔
413
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_userMetadata);
2,473✔
414
    Query query = table->where().equal(schema.identity_col, StringData(identity));
2,473✔
415
    Results results(realm, std::move(query));
2,473✔
416
    REALM_ASSERT_DEBUG(results.size() < 2);
2,473✔
417
    auto obj = results.first();
2,473✔
418

1,213✔
419
    if (!obj) {
2,473✔
420
        if (!make_if_absent)
1,160✔
421
            return none;
6✔
422

566✔
423
        realm->begin_transaction();
1,154✔
424
        // Check the results again.
566✔
425
        obj = results.first();
1,154✔
426
    }
1,154✔
427
    if (!obj) {
2,470✔
428
        // Because "making this user" is our last action, set this new user as the current user
566✔
429
        TableRef currentUserIdentityTable =
1,154✔
430
            ObjectStore::table_for_object_type(realm->read_group(), c_sync_current_user_identity);
1,154✔
431

566✔
432
        Obj currentUserIdentityObj;
1,154✔
433
        if (currentUserIdentityTable->is_empty())
1,154✔
434
            currentUserIdentityObj = currentUserIdentityTable->create_object();
603✔
435
        else
551✔
436
            currentUserIdentityObj = *currentUserIdentityTable->begin();
551✔
437

566✔
438
        obj = table->create_object();
1,154✔
439

566✔
440
        currentUserIdentityObj.set<String>(c_sync_current_user_identity, identity);
1,154✔
441

566✔
442
        obj->set(schema.identity_col, identity);
1,154✔
443
        obj->set(schema.state_col, (int64_t)SyncUser::State::LoggedIn);
1,154✔
444
        realm->commit_transaction();
1,154✔
445
        return SyncUserMetadata(schema, std::move(realm), *obj);
1,154✔
446
    }
1,154✔
447

644✔
448
    // Got an existing user.
644✔
449
    if (obj->get<int64_t>(schema.state_col) == int64_t(SyncUser::State::Removed)) {
1,313✔
450
        // User is dead. Revive or return none.
4✔
451
        if (!make_if_absent) {
8✔
452
            return none;
4✔
453
        }
4✔
454

2✔
455
        if (!realm->is_in_transaction())
4✔
456
            realm->begin_transaction();
4✔
457
        obj->set(schema.state_col, (int64_t)SyncUser::State::LoggedIn);
4✔
458
        realm->commit_transaction();
4✔
459
    }
4✔
460

644✔
461
    return SyncUserMetadata(schema, std::move(realm), std::move(*obj));
1,311✔
462
}
1,313✔
463

464
void SyncMetadataManager::make_file_action_metadata(StringData original_name, StringData partition_key_value,
465
                                                    StringData local_uuid, SyncFileActionMetadata::Action action,
466
                                                    StringData new_name) const
467
{
148✔
468
    auto realm = get_realm();
148✔
469
    realm->begin_transaction();
148✔
470
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata);
148✔
471

74✔
472
    auto& schema = m_file_action_schema;
148✔
473
    Obj obj = table->create_object_with_primary_key(original_name);
148✔
474

74✔
475
    obj.set(schema.idx_new_name, new_name);
148✔
476
    obj.set(schema.idx_action, static_cast<int64_t>(action));
148✔
477
    obj.set(schema.idx_partition, partition_key_value);
148✔
478
    obj.set(schema.idx_user_identity, local_uuid);
148✔
479
    realm->commit_transaction();
148✔
480
}
148✔
481

482
util::Optional<SyncFileActionMetadata> SyncMetadataManager::get_file_action_metadata(StringData original_name) const
483
{
26✔
484
    auto realm = get_realm();
26✔
485
    auto& schema = m_file_action_schema;
26✔
486
    TableRef table = ObjectStore::table_for_object_type(realm->read_group(), c_sync_fileActionMetadata);
26✔
487
    auto row_idx = table->find_first_string(schema.idx_original_name, original_name);
26✔
488
    if (!row_idx)
26✔
489
        return none;
8✔
490

9✔
491
    return SyncFileActionMetadata(std::move(schema), std::move(realm), table->get_object(row_idx));
18✔
492
}
18✔
493

494
bool SyncMetadataManager::perform_file_actions(SyncFileManager& file_manager, StringData path) const
495
{
14✔
496
    if (auto md = get_file_action_metadata(path)) {
14✔
497
        return run_file_action(file_manager, *md);
6✔
498
    }
6✔
499
    return false;
8✔
500
}
8✔
501

502
std::shared_ptr<Realm> SyncMetadataManager::get_realm() const
503
{
5,961✔
504
    auto realm = Realm::get_shared_realm(m_metadata_config);
5,961✔
505
    realm->refresh();
5,961✔
506
    return realm;
5,961✔
507
}
5,961✔
508

509
std::shared_ptr<Realm> SyncMetadataManager::try_get_realm() const
510
{
755✔
511
    try {
755✔
512
        return get_realm();
755✔
513
    }
755✔
514
    catch (const InvalidDatabase&) {
4✔
515
        return nullptr;
4✔
516
    }
4✔
517
}
755✔
518

519
std::shared_ptr<Realm> SyncMetadataManager::open_realm(bool should_encrypt, bool caller_supplied_key)
520
{
755✔
521
    if (caller_supplied_key || !should_encrypt || !REALM_PLATFORM_APPLE) {
755✔
522
        if (auto realm = try_get_realm())
755✔
523
            return realm;
751✔
524

2✔
525
        // Encryption key changed, so delete the existing metadata realm and
2✔
526
        // recreate it
2✔
527
        util::File::remove(m_metadata_config.path);
4✔
528
        return get_realm();
4✔
529
    }
4✔
530

531
#if REALM_PLATFORM_APPLE
532
    // This logic is all a giant race condition once we have multi-process sync.
533
    // Wrapping it all (including the keychain accesses) in DB::call_with_lock()
534
    // might suffice.
535

536
    // First try to open the Realm with a key already stored in the keychain.
537
    // This works for both the case where everything is sensible and valid and
538
    // when we have a key but no metadata Realm.
539
    auto key = keychain::get_existing_metadata_realm_key();
540
    if (key) {
×
541
        m_metadata_config.encryption_key = *key;
542
        if (auto realm = try_get_realm())
×
543
            return realm;
544
    }
545

546
    // If we have an existing file and either no key or the key didn't work to
547
    // decrypt it, then we might have an unencrypted metadata Realm resulting
548
    // from a previous run being unable to access the keychain.
549
    if (util::File::exists(m_metadata_config.path)) {
×
550
        m_metadata_config.encryption_key.clear();
551
        if (auto realm = try_get_realm())
×
552
            return realm;
553

554
        // We weren't able to open the existing file with either the stored key
555
        // or no key, so just delete it.
556
        util::File::remove(m_metadata_config.path);
557
    }
558

559
    // We now have no metadata Realm. If we don't have an existing stored key,
560
    // try to create and store a new one. This might fail, in which case we
561
    // just create an unencrypted Realm file.
562
    if (!key)
×
563
        key = keychain::create_new_metadata_realm_key();
564
    if (key)
×
565
        m_metadata_config.encryption_key = std::move(*key);
566
    return get_realm();
567
#else  // REALM_PLATFORM_APPLE
568
    REALM_UNREACHABLE();
569
#endif // REALM_PLATFORM_APPLE
570
}
×
571

572
// MARK: - Sync user metadata
573

574
SyncUserMetadata::SyncUserMetadata(Schema schema, SharedRealm realm, const Obj& obj)
575
    : m_realm(std::move(realm))
576
    , m_schema(std::move(schema))
577
    , m_obj(obj)
578
{
2,535✔
579
}
2,535✔
580

581
std::string SyncUserMetadata::identity() const
582
{
158✔
583
    REALM_ASSERT(m_realm);
158✔
584
    m_realm->refresh();
158✔
585
    return m_obj.get<String>(m_schema.identity_col);
158✔
586
}
158✔
587

588
SyncUser::State SyncUserMetadata::state() const
589
{
50✔
590
    REALM_ASSERT(m_realm);
50✔
591
    m_realm->refresh();
50✔
592
    return SyncUser::State(m_obj.get<int64_t>(m_schema.state_col));
50✔
593
}
50✔
594

595
std::vector<std::string> SyncUserMetadata::legacy_identities() const
596
{
1,166✔
597
    REALM_ASSERT(m_realm);
1,166✔
598
    m_realm->refresh();
1,166✔
599
    std::vector<std::string> uuids;
1,166✔
600
    auto list = m_obj.get_list<String>(m_schema.legacy_uuids_col);
1,166✔
601
    for (size_t i = 0, size = list.size(); i < size; ++i) {
1,224✔
602
        uuids.push_back(list.get(i));
58✔
603
    }
58✔
604
    return uuids;
1,166✔
605
}
1,166✔
606

607
std::string SyncUserMetadata::refresh_token() const
608
{
48✔
609
    REALM_ASSERT(m_realm);
48✔
610
    m_realm->refresh();
48✔
611
    StringData result = m_obj.get<String>(m_schema.refresh_token_col);
48✔
612
    return result.is_null() ? "" : std::string(result);
48✔
613
}
48✔
614

615
std::string SyncUserMetadata::access_token() const
616
{
86✔
617
    REALM_ASSERT(m_realm);
86✔
618
    StringData result = m_obj.get<String>(m_schema.access_token_col);
86✔
619
    return result.is_null() ? "" : std::string(result);
80✔
620
}
86✔
621

622
std::string SyncUserMetadata::device_id() const
623
{
36✔
624
    REALM_ASSERT(m_realm);
36✔
625
    StringData result = m_obj.get<String>(m_schema.device_id_col);
36✔
626
    return result.is_null() ? "" : std::string(result);
36✔
627
}
36✔
628

629
std::vector<SyncUserIdentity> SyncUserMetadata::identities() const
630
{
48✔
631
    REALM_ASSERT(m_realm);
48✔
632
    m_realm->refresh();
48✔
633
    auto linklist = m_obj.get_linklist(m_schema.identities_col);
48✔
634

24✔
635
    std::vector<SyncUserIdentity> identities;
48✔
636
    for (size_t i = 0; i < linklist.size(); i++) {
132✔
637
        auto obj = linklist.get_object(i);
84✔
638
        identities.emplace_back(obj.get<String>(c_sync_user_id), obj.get<String>(c_sync_provider_type));
84✔
639
    }
84✔
640

24✔
641
    return identities;
48✔
642
}
48✔
643

644
SyncUserProfile SyncUserMetadata::profile() const
645
{
1,134✔
646
    REALM_ASSERT(m_realm);
1,134✔
647
    m_realm->refresh();
1,134✔
648
    StringData result = m_obj.get<String>(m_schema.profile_dump_col);
1,134✔
649
    if (result.size() == 0) {
1,134✔
650
        return SyncUserProfile();
1,106✔
651
    }
1,106✔
652
    return SyncUserProfile(static_cast<bson::BsonDocument>(bson::parse(std::string_view(result))));
28✔
653
}
28✔
654

655
void SyncUserMetadata::set_refresh_token(const std::string& refresh_token)
656
{
12✔
657
    if (m_invalid)
12✔
658
        return;
×
659

6✔
660
    REALM_ASSERT_DEBUG(m_realm);
12✔
661
    m_realm->begin_transaction();
12✔
662
    m_obj.set<String>(m_schema.refresh_token_col, refresh_token);
12✔
663
    m_realm->commit_transaction();
12✔
664
}
12✔
665

666
void SyncUserMetadata::set_state(SyncUser::State state)
667
{
6✔
668
    if (m_invalid)
6✔
669
        return;
×
670

3✔
671
    REALM_ASSERT_DEBUG(m_realm);
6✔
672
    m_realm->begin_transaction();
6✔
673
    m_obj.set<int64_t>(m_schema.state_col, (int64_t)state);
6✔
674
    m_realm->commit_transaction();
6✔
675
}
6✔
676

677
void SyncUserMetadata::set_state_and_tokens(SyncUser::State state, const std::string& access_token,
678
                                            const std::string& refresh_token)
679
{
1,178✔
680
    if (m_invalid)
1,178✔
681
        return;
×
682

577✔
683
    REALM_ASSERT_DEBUG(m_realm);
1,178✔
684
    m_realm->begin_transaction();
1,178✔
685
    m_obj.set(m_schema.state_col, static_cast<int64_t>(state));
1,178✔
686
    m_obj.set(m_schema.access_token_col, access_token);
1,178✔
687
    m_obj.set(m_schema.refresh_token_col, refresh_token);
1,178✔
688
    m_realm->commit_transaction();
1,178✔
689
}
1,178✔
690

691
void SyncUserMetadata::set_identities(std::vector<SyncUserIdentity> identities)
692
{
1,074✔
693
    if (m_invalid)
1,074✔
694
        return;
×
695

526✔
696
    REALM_ASSERT_DEBUG(m_realm);
1,074✔
697
    m_realm->begin_transaction();
1,074✔
698

526✔
699
    auto link_list = m_obj.get_linklist(m_schema.identities_col);
1,074✔
700
    auto identities_table = link_list.get_target_table();
1,074✔
701
    auto col_user_id = identities_table->get_column_key(c_sync_user_id);
1,074✔
702
    auto col_provider_type = identities_table->get_column_key(c_sync_provider_type);
1,074✔
703
    link_list.clear();
1,074✔
704

526✔
705
    for (auto& ident : identities) {
1,114✔
706
        auto obj = link_list.create_and_insert_linked_object(link_list.size());
1,114✔
707
        obj.set<String>(col_user_id, ident.id);
1,114✔
708
        obj.set<String>(col_provider_type, ident.provider_type);
1,114✔
709
    }
1,114✔
710

526✔
711
    m_realm->commit_transaction();
1,074✔
712
}
1,074✔
713

714
void SyncUserMetadata::set_access_token(const std::string& user_token)
715
{
81✔
716
    if (m_invalid)
81✔
717
        return;
×
718

40✔
719
    REALM_ASSERT_DEBUG(m_realm);
81✔
720
    m_realm->begin_transaction();
81✔
721
    m_obj.set(m_schema.access_token_col, user_token);
81✔
722
    m_realm->commit_transaction();
81✔
723
}
81✔
724

725
void SyncUserMetadata::set_device_id(const std::string& device_id)
726
{
1,114✔
727
    if (m_invalid)
1,114✔
728
        return;
×
729

546✔
730
    REALM_ASSERT_DEBUG(m_realm);
1,114✔
731
    m_realm->begin_transaction();
1,114✔
732
    m_obj.set(m_schema.device_id_col, device_id);
1,114✔
733
    m_realm->commit_transaction();
1,114✔
734
}
1,114✔
735

736
void SyncUserMetadata::set_legacy_identities(const std::vector<std::string>& uuids)
737
{
12✔
738
    m_realm->begin_transaction();
12✔
739
    auto list = m_obj.get_list<String>(m_schema.legacy_uuids_col);
12✔
740
    list.clear();
12✔
741
    for (auto& uuid : uuids)
12✔
742
        list.add(uuid);
12✔
743
    m_realm->commit_transaction();
12✔
744
}
12✔
745

746
void SyncUserMetadata::set_user_profile(const SyncUserProfile& profile)
747
{
1,074✔
748
    if (m_invalid)
1,074✔
749
        return;
×
750

526✔
751
    REALM_ASSERT_DEBUG(m_realm);
1,074✔
752
    m_realm->begin_transaction();
1,074✔
753
    std::stringstream data;
1,074✔
754
    data << profile.data();
1,074✔
755
    m_obj.set(m_schema.profile_dump_col, data.str());
1,074✔
756
    m_realm->commit_transaction();
1,074✔
757
}
1,074✔
758

759
std::vector<std::string> SyncUserMetadata::realm_file_paths() const
760
{
26✔
761
    if (m_invalid)
26✔
762
        return {};
×
763

13✔
764
    REALM_ASSERT_DEBUG(m_realm);
26✔
765
    m_realm->refresh();
26✔
766
    Set<StringData> paths = m_obj.get_set<StringData>(m_schema.realm_file_paths_col);
26✔
767
    return std::vector<std::string>(paths.begin(), paths.end());
26✔
768
}
26✔
769

770
void SyncUserMetadata::add_realm_file_path(const std::string& path)
771
{
46✔
772
    if (m_invalid)
46✔
773
        return;
×
774

23✔
775
    REALM_ASSERT_DEBUG(m_realm);
46✔
776
    m_realm->begin_transaction();
46✔
777
    Set<StringData> paths = m_obj.get_set<StringData>(m_schema.realm_file_paths_col);
46✔
778
    paths.insert(path);
46✔
779
    m_realm->commit_transaction();
46✔
780
}
46✔
781

782
void SyncUserMetadata::remove()
783
{
24✔
784
    m_invalid = true;
24✔
785
    m_realm->begin_transaction();
24✔
786
    m_obj.remove();
24✔
787
    m_realm->commit_transaction();
24✔
788
    m_realm = nullptr;
24✔
789
}
24✔
790

791
// MARK: - File action metadata
792

793
SyncFileActionMetadata::SyncFileActionMetadata(Schema schema, SharedRealm realm, const Obj& obj)
794
    : m_realm(std::move(realm))
795
    , m_schema(std::move(schema))
796
    , m_obj(obj)
797
{
84✔
798
}
84✔
799

800
std::string SyncFileActionMetadata::original_name() const
801
{
78✔
802
    REALM_ASSERT(m_realm);
78✔
803
    m_realm->refresh();
78✔
804
    return m_obj.get<String>(m_schema.idx_original_name);
78✔
805
}
78✔
806

807
util::Optional<std::string> SyncFileActionMetadata::new_name() const
808
{
50✔
809
    REALM_ASSERT(m_realm);
50✔
810
    m_realm->refresh();
50✔
811
    StringData result = m_obj.get<String>(m_schema.idx_new_name);
50✔
812
    return result.is_null() ? util::none : util::make_optional(std::string(result));
49✔
813
}
50✔
814

815
std::string SyncFileActionMetadata::user_local_uuid() const
816
{
6✔
817
    REALM_ASSERT(m_realm);
6✔
818
    m_realm->refresh();
6✔
819
    return m_obj.get<String>(m_schema.idx_user_identity);
6✔
820
}
6✔
821

822
SyncFileActionMetadata::Action SyncFileActionMetadata::action() const
823
{
66✔
824
    REALM_ASSERT(m_realm);
66✔
825
    m_realm->refresh();
66✔
826
    return static_cast<SyncFileActionMetadata::Action>(m_obj.get<Int>(m_schema.idx_action));
66✔
827
}
66✔
828

829
std::string SyncFileActionMetadata::partition() const
830
{
6✔
831
    REALM_ASSERT(m_realm);
6✔
832
    m_realm->refresh();
6✔
833
    return m_obj.get<String>(m_schema.idx_partition);
6✔
834
}
6✔
835

836
void SyncFileActionMetadata::remove()
837
{
58✔
838
    REALM_ASSERT(m_realm);
58✔
839
    m_realm->begin_transaction();
58✔
840
    m_obj.remove();
58✔
841
    m_realm->commit_transaction();
58✔
842
    m_realm = nullptr;
58✔
843
}
58✔
844

845
void SyncFileActionMetadata::set_action(Action new_action)
846
{
2✔
847
    REALM_ASSERT(m_realm);
2✔
848
    m_realm->begin_transaction();
2✔
849
    m_obj.set<Int>(m_schema.idx_action, static_cast<Int>(new_action));
2✔
850
    m_realm->commit_transaction();
2✔
851
}
2✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc