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

realm / realm-core / 1691

20 Sep 2023 01:57AM UTC coverage: 91.217% (+0.05%) from 91.168%
1691

push

Evergreen

web-flow
Merge pull request #6837 from realm/tg/user-provider

Fix handling of users with multiple identities

95990 of 175908 branches covered (0.0%)

799 of 830 new or added lines in 24 files covered. (96.27%)

44 existing lines in 15 files now uncovered.

233732 of 256237 relevant lines covered (91.22%)

6741306.52 hits per line

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

90.57
/src/realm/object-store/sync/sync_user.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/sync_user.hpp>
20

21
#include <realm/object-store/sync/app.hpp>
22
#include <realm/object-store/sync/app_credentials.hpp>
23
#include <realm/object-store/sync/generic_network_transport.hpp>
24
#include <realm/object-store/sync/impl/sync_metadata.hpp>
25
#include <realm/object-store/sync/mongo_client.hpp>
26
#include <realm/object-store/sync/sync_manager.hpp>
27
#include <realm/object-store/sync/sync_session.hpp>
28

29
#include <realm/util/base64.hpp>
30

31
namespace realm {
32

33
static std::string base64_decode(const std::string& in)
34
{
15,806✔
35
    std::string out;
15,806✔
36
    out.resize(util::base64_decoded_size(in.size()));
15,806✔
37
    util::base64_decode(in, &out[0], out.size());
15,806✔
38
    return out;
15,806✔
39
}
15,806✔
40

41
static std::vector<std::string> split_token(const std::string& jwt)
42
{
15,810✔
43
    constexpr static char delimiter = '.';
15,810✔
44

7,825✔
45
    std::vector<std::string> parts;
15,810✔
46
    size_t pos = 0, start_from = 0;
15,810✔
47

7,825✔
48
    while ((pos = jwt.find(delimiter, start_from)) != std::string::npos) {
47,422✔
49
        parts.push_back(jwt.substr(start_from, pos - start_from));
31,612✔
50
        start_from = pos + 1;
31,612✔
51
    }
31,612✔
52

7,825✔
53
    parts.push_back(jwt.substr(start_from));
15,810✔
54

7,825✔
55
    if (parts.size() != 3) {
15,810✔
56
        throw app::AppError(ErrorCodes::BadToken, "jwt missing parts");
4✔
57
    }
4✔
58

7,823✔
59
    return parts;
15,806✔
60
}
15,806✔
61

62
RealmJWT::RealmJWT(const std::string& token)
63
    : token(token)
64
{
15,810✔
65
    auto parts = split_token(this->token);
15,810✔
66

7,825✔
67
    auto json_str = base64_decode(parts[1]);
15,810✔
68
    auto json = static_cast<bson::BsonDocument>(bson::parse(json_str));
15,810✔
69

7,825✔
70
    this->expires_at = static_cast<int64_t>(json["exp"]);
15,810✔
71
    this->issued_at = static_cast<int64_t>(json["iat"]);
15,810✔
72

7,825✔
73
    if (json.find("user_data") != json.end()) {
15,810✔
74
        this->user_data = static_cast<bson::BsonDocument>(json["user_data"]);
13,762✔
75
    }
13,762✔
76
}
15,810✔
77

78
SyncUserIdentity::SyncUserIdentity(const std::string& id, const std::string& provider_type)
79
    : id(id)
80
    , provider_type(provider_type)
81
{
1,092✔
82
}
1,092✔
83

84
SyncUserContextFactory SyncUser::s_binding_context_factory;
85
std::mutex SyncUser::s_binding_context_factory_mutex;
86

87
SyncUser::SyncUser(const std::string& refresh_token, const std::string& id, const std::string& access_token,
88
                   const std::string& device_id, SyncManager* sync_manager)
89
    : m_state(State::LoggedIn)
90
    , m_identity(id)
91
    , m_refresh_token(RealmJWT(refresh_token))
92
    , m_access_token(RealmJWT(access_token))
93
    , m_device_id(device_id)
94
    , m_sync_manager(sync_manager)
95
{
4,520✔
96
    REALM_ASSERT(!access_token.empty() && !refresh_token.empty());
4,520✔
97
    {
4,520✔
98
        std::lock_guard lock(s_binding_context_factory_mutex);
4,520✔
99
        if (s_binding_context_factory) {
4,520✔
100
            m_binding_context = s_binding_context_factory();
×
101
        }
×
102
    }
4,520✔
103

2,222✔
104
    m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS {
4,500✔
105
        auto metadata = manager.get_or_make_user_metadata(m_identity);
4,480✔
106
        metadata->set_state_and_tokens(State::LoggedIn, m_access_token.token, m_refresh_token.token);
4,480✔
107
        metadata->set_device_id(m_device_id);
4,480✔
108
        m_legacy_identities = metadata->legacy_identities();
4,480✔
109
        this->m_user_profile = metadata->profile();
4,480✔
110
    });
4,480✔
111
}
4,520✔
112

113
SyncUser::SyncUser(const SyncUserMetadata& data, SyncManager* sync_manager)
114
    : m_state(data.state())
115
    , m_legacy_identities(data.legacy_identities())
116
    , m_identity(data.identity())
117
    , m_refresh_token(RealmJWT(data.refresh_token()))
118
    , m_access_token(RealmJWT(data.access_token()))
119
    , m_user_identities(data.identities())
120
    , m_user_profile(data.profile())
121
    , m_device_id(data.device_id())
122
    , m_sync_manager(sync_manager)
123
{
12✔
124
    // Check for inconsistent state in the metadata Realm. This shouldn't happen,
6✔
125
    // but previous versions could sometimes mark a user as logged in with an
6✔
126
    // empty refresh token.
6✔
127
    if (m_state == State::LoggedIn && (m_refresh_token.token.empty() || m_access_token.token.empty())) {
12✔
NEW
128
        m_state = State::LoggedOut;
×
NEW
129
        m_refresh_token = {};
×
NEW
130
        m_access_token = {};
×
NEW
131
    }
×
132

6✔
133
    {
12✔
134
        std::lock_guard lock(s_binding_context_factory_mutex);
12✔
135
        if (s_binding_context_factory) {
12✔
NEW
136
            m_binding_context = s_binding_context_factory();
×
NEW
137
        }
×
138
    }
12✔
139
}
12✔
140

141
std::shared_ptr<SyncManager> SyncUser::sync_manager() const
142
{
2,625✔
143
    util::CheckedLockGuard lk(m_mutex);
2,625✔
144
    if (m_state == State::Removed) {
2,625✔
145
        throw app::AppError(
6✔
146
            ErrorCodes::ClientUserNotFound,
6✔
147
            util::format("Cannot start a sync session for user '%1' because this user has been removed.",
6✔
148
                         m_identity));
6✔
149
    }
6✔
150
    REALM_ASSERT(m_sync_manager);
2,619✔
151
    return m_sync_manager->shared_from_this();
2,619✔
152
}
2,619✔
153

154
void SyncUser::detach_from_sync_manager()
155
{
4,530✔
156
    util::CheckedLockGuard lk(m_mutex);
4,530✔
157
    REALM_ASSERT(m_sync_manager);
4,530✔
158
    m_state = SyncUser::State::Removed;
4,530✔
159
    m_sync_manager = nullptr;
4,530✔
160
}
4,530✔
161

162
std::vector<std::shared_ptr<SyncSession>> SyncUser::all_sessions()
163
{
20✔
164
    util::CheckedLockGuard lock(m_mutex);
20✔
165
    std::vector<std::shared_ptr<SyncSession>> sessions;
20✔
166
    if (m_state == State::Removed) {
20✔
167
        return sessions;
×
168
    }
×
169
    for (auto it = m_sessions.begin(); it != m_sessions.end();) {
42✔
170
        if (auto ptr_to_session = it->second.lock()) {
22✔
171
            sessions.emplace_back(std::move(ptr_to_session));
22✔
172
            it++;
22✔
173
            continue;
22✔
174
        }
22✔
175
        // This session is bad, destroy it.
176
        it = m_sessions.erase(it);
×
177
    }
×
178
    return sessions;
20✔
179
}
20✔
180

181
std::shared_ptr<SyncSession> SyncUser::session_for_on_disk_path(const std::string& path)
182
{
1,542✔
183
    util::CheckedLockGuard lock(m_mutex);
1,542✔
184
    if (m_state == State::Removed) {
1,542✔
185
        return nullptr;
×
186
    }
×
187
    auto it = m_sessions.find(path);
1,542✔
188
    if (it == m_sessions.end()) {
1,542✔
189
        return nullptr;
×
190
    }
×
191
    auto locked = it->second.lock();
1,542✔
192
    if (!locked) {
1,542✔
193
        // Remove the session from the map, because it has fatally errored out or the entry is invalid.
194
        m_sessions.erase(it);
×
195
    }
×
196
    return locked;
1,542✔
197
}
1,542✔
198

199
void SyncUser::log_in(const std::string& access_token, const std::string& refresh_token)
200
{
3,338✔
201
    REALM_ASSERT(!access_token.empty());
3,338✔
202
    REALM_ASSERT(!refresh_token.empty());
3,338✔
203
    std::vector<std::shared_ptr<SyncSession>> sessions_to_revive;
3,338✔
204
    {
3,338✔
205
        util::CheckedLockGuard lock1(m_mutex);
3,338✔
206
        util::CheckedLockGuard lock2(m_tokens_mutex);
3,338✔
207
        m_state = State::LoggedIn;
3,338✔
208
        m_access_token = RealmJWT(access_token);
3,338✔
209
        m_refresh_token = RealmJWT(refresh_token);
3,338✔
210
        sessions_to_revive = revive_sessions();
3,338✔
211

1,667✔
212
        m_sync_manager->perform_metadata_update([&](const auto& manager) {
3,336✔
213
            auto metadata = manager.get_or_make_user_metadata(m_identity);
3,334✔
214
            metadata->set_state_and_tokens(State::LoggedIn, access_token, refresh_token);
3,334✔
215
        });
3,334✔
216
    }
3,338✔
217
    // (Re)activate all pending sessions.
1,667✔
218
    // Note that we do this after releasing the lock, since the session may
1,667✔
219
    // need to access protected User state in the process of binding itself.
1,667✔
220
    for (auto& session : sessions_to_revive) {
1,673✔
221
        session->revive_if_needed();
12✔
222
    }
12✔
223

1,667✔
224
    emit_change_to_subscribers(*this);
3,338✔
225
}
3,338✔
226

227
void SyncUser::invalidate()
228
{
27✔
229
    {
27✔
230
        util::CheckedLockGuard lock1(m_mutex);
27✔
231
        util::CheckedLockGuard lock2(m_tokens_mutex);
27✔
232
        m_state = State::Removed;
27✔
233
        m_access_token = {};
27✔
234
        m_refresh_token = {};
27✔
235

13✔
236
        m_sync_manager->perform_metadata_update([&](const auto& manager) {
27✔
237
            auto metadata = manager.get_or_make_user_metadata(m_identity);
27✔
238
            metadata->set_state_and_tokens(State::Removed, "", "");
27✔
239
        });
27✔
240
    }
27✔
241
    emit_change_to_subscribers(*this);
27✔
242
}
27✔
243

244
std::vector<std::shared_ptr<SyncSession>> SyncUser::revive_sessions()
245
{
3,338✔
246
    std::vector<std::shared_ptr<SyncSession>> sessions_to_revive;
3,338✔
247
    sessions_to_revive.reserve(m_waiting_sessions.size());
3,338✔
248
    for (auto& [path, weak_session] : m_waiting_sessions) {
1,675✔
249
        if (auto ptr = weak_session.lock()) {
16✔
250
            m_sessions[path] = ptr;
12✔
251
            sessions_to_revive.emplace_back(std::move(ptr));
12✔
252
        }
12✔
253
    }
16✔
254
    m_waiting_sessions.clear();
3,338✔
255
    return sessions_to_revive;
3,338✔
256
}
3,338✔
257

258
void SyncUser::update_access_token(std::string&& token)
259
{
66✔
260
    {
66✔
261
        util::CheckedLockGuard lock(m_mutex);
66✔
262
        if (m_state != State::LoggedIn)
66✔
263
            return;
2✔
264

32✔
265
        util::CheckedLockGuard lock2(m_tokens_mutex);
64✔
266
        m_access_token = RealmJWT(std::move(token));
64✔
267
        m_sync_manager->perform_metadata_update([&, raw_access_token = m_access_token.token](const auto& manager) {
63✔
268
            auto metadata = manager.get_or_make_user_metadata(m_identity);
61✔
269
            metadata->set_access_token(raw_access_token);
61✔
270
        });
61✔
271
    }
64✔
272

32✔
273
    emit_change_to_subscribers(*this);
64✔
274
}
64✔
275

276
std::vector<SyncUserIdentity> SyncUser::identities() const
277
{
30✔
278
    util::CheckedLockGuard lock(m_mutex);
30✔
279
    return m_user_identities;
30✔
280
}
30✔
281

282
void SyncUser::log_out()
283
{
99✔
284
    // We'll extend the lifetime of SyncManager while holding m_mutex so that we know it's safe to call methods on it
49✔
285
    // after we've been marked as logged out.
49✔
286
    std::shared_ptr<SyncManager> sync_manager_shared;
99✔
287
    {
99✔
288
        util::CheckedLockGuard lock(m_mutex);
99✔
289
        bool is_anonymous = false;
99✔
290
        {
99✔
291
            util::CheckedLockGuard lock2(m_tokens_mutex);
99✔
292
            if (m_state != State::LoggedIn) {
99✔
293
                return;
×
294
            }
×
295
            is_anonymous = do_is_anonymous();
99✔
296
            m_state = State::LoggedOut;
99✔
297
            m_access_token = RealmJWT{};
99✔
298
            m_refresh_token = RealmJWT{};
99✔
299
        }
99✔
300

49✔
301
        if (is_anonymous) {
99✔
302
            // An Anonymous user can not log back in.
10✔
303
            // Mark the user as 'dead' in the persisted metadata Realm.
10✔
304
            m_state = State::Removed;
20✔
305
            m_sync_manager->perform_metadata_update([&](const auto& manager) {
20✔
306
                auto metadata = manager.get_or_make_user_metadata(m_identity, false);
20✔
307
                if (metadata)
20✔
308
                    metadata->remove();
20✔
309
            });
20✔
310
        }
20✔
311
        else {
79✔
312
            m_sync_manager->perform_metadata_update([&](const auto& manager) {
75✔
313
                auto metadata = manager.get_or_make_user_metadata(m_identity);
71✔
314
                metadata->set_state_and_tokens(State::LoggedOut, "", "");
71✔
315
            });
71✔
316
        }
79✔
317
        sync_manager_shared = m_sync_manager->shared_from_this();
99✔
318
        // Move all active sessions into the waiting sessions pool. If the user is
49✔
319
        // logged back in, they will automatically be reactivated.
49✔
320
        for (auto& [path, weak_session] : m_sessions) {
68✔
321
            if (auto ptr = weak_session.lock()) {
37✔
322
                ptr->force_close();
35✔
323
                m_waiting_sessions[path] = std::move(ptr);
35✔
324
            }
35✔
325
        }
37✔
326
        m_sessions.clear();
99✔
327
    }
99✔
328

49✔
329
    sync_manager_shared->log_out_user(*this);
99✔
330
    emit_change_to_subscribers(*this);
99✔
331
}
99✔
332

333
bool SyncUser::is_logged_in() const
334
{
1,677✔
335
    util::CheckedLockGuard lock(m_mutex);
1,677✔
336
    return m_state == State::LoggedIn;
1,677✔
337
}
1,677✔
338

339
bool SyncUser::is_anonymous() const
340
{
40✔
341
    util::CheckedLockGuard lock(m_mutex);
40✔
342
    util::CheckedLockGuard lock2(m_tokens_mutex);
40✔
343
    return do_is_anonymous();
40✔
344
}
40✔
345

346
bool SyncUser::do_is_anonymous() const
347
{
139✔
348
    return m_state == State::LoggedIn && m_user_identities.size() == 1 &&
139✔
349
           m_user_identities[0].provider_type == app::IdentityProviderAnonymous;
108✔
350
}
139✔
351

352
std::string SyncUser::refresh_token() const
353
{
218✔
354
    util::CheckedLockGuard lock(m_tokens_mutex);
218✔
355
    return m_refresh_token.token;
218✔
356
}
218✔
357

358
std::string SyncUser::access_token() const
359
{
3,246✔
360
    util::CheckedLockGuard lock(m_tokens_mutex);
3,246✔
361
    return m_access_token.token;
3,246✔
362
}
3,246✔
363

364
std::string SyncUser::device_id() const
365
{
36✔
366
    util::CheckedLockGuard lock(m_mutex);
36✔
367
    return m_device_id;
36✔
368
}
36✔
369

370
bool SyncUser::has_device_id() const
371
{
34✔
372
    util::CheckedLockGuard lock(m_mutex);
34✔
373
    return !m_device_id.empty() && m_device_id != "000000000000000000000000";
34✔
374
}
34✔
375

376
SyncUser::State SyncUser::state() const
377
{
7,145✔
378
    util::CheckedLockGuard lock(m_mutex);
7,145✔
379
    return m_state;
7,145✔
380
}
7,145✔
381

382
SyncUserProfile SyncUser::user_profile() const
383
{
78✔
384
    util::CheckedLockGuard lock(m_mutex);
78✔
385
    return m_user_profile;
78✔
386
}
78✔
387

388
util::Optional<bson::BsonDocument> SyncUser::custom_data() const
389
{
4✔
390
    util::CheckedLockGuard lock(m_tokens_mutex);
4✔
391
    return m_access_token.user_data;
4✔
392
}
4✔
393

394
void SyncUser::update_user_profile(std::vector<SyncUserIdentity> identities, SyncUserProfile profile)
395
{
1,004✔
396
    util::CheckedLockGuard lock(m_mutex);
1,004✔
397
    if (m_state == SyncUser::State::Removed) {
1,004✔
UNCOV
398
        return;
×
UNCOV
399
    }
×
400

491✔
401
    m_user_identities = std::move(identities);
1,004✔
402
    m_user_profile = std::move(profile);
1,004✔
403

491✔
404
    m_sync_manager->perform_metadata_update([&](const auto& manager) NO_THREAD_SAFETY_ANALYSIS {
1,003✔
405
        auto metadata = manager.get_or_make_user_metadata(m_identity);
1,002✔
406
        metadata->set_identities(m_user_identities);
1,002✔
407
        metadata->set_user_profile(m_user_profile);
1,002✔
408
    });
1,002✔
409
}
1,004✔
410

411
void SyncUser::register_session(std::shared_ptr<SyncSession> session)
412
{
1,349✔
413
    const std::string& path = session->path();
1,349✔
414
    util::CheckedUniqueLock lock(m_mutex);
1,349✔
415
    switch (m_state) {
1,349✔
416
        case State::LoggedIn:
1,345✔
417
            m_sessions[path] = session;
1,345✔
418
            break;
1,345✔
419
        case State::LoggedOut:
4✔
420
            m_waiting_sessions[path] = session;
4✔
421
            break;
4✔
422
        case State::Removed:
✔
423
            break;
×
424
    }
1,349✔
425
}
1,349✔
426

427
app::MongoClient SyncUser::mongo_client(const std::string& service_name)
428
{
57✔
429
    util::CheckedLockGuard lk(m_mutex);
57✔
430
    REALM_ASSERT(m_state == SyncUser::State::LoggedIn);
57✔
431
    return app::MongoClient(shared_from_this(), m_sync_manager->app().lock(), service_name);
57✔
432
}
57✔
433

434
void SyncUser::set_binding_context_factory(SyncUserContextFactory factory)
435
{
×
436
    std::lock_guard<std::mutex> lock(s_binding_context_factory_mutex);
×
437
    s_binding_context_factory = std::move(factory);
×
438
}
×
439

440
void SyncUser::refresh_custom_data(util::UniqueFunction<void(util::Optional<app::AppError>)> completion_block)
441
    REQUIRES(!m_mutex)
442
{
30✔
443
    refresh_custom_data(false, std::move(completion_block));
30✔
444
}
30✔
445

446
void SyncUser::refresh_custom_data(bool update_location,
447
                                   util::UniqueFunction<void(util::Optional<app::AppError>)> completion_block)
448
{
54✔
449
    std::shared_ptr<app::App> app;
54✔
450
    std::shared_ptr<SyncUser> user;
54✔
451
    {
54✔
452
        util::CheckedLockGuard lk(m_mutex);
54✔
453
        if (m_state != SyncUser::State::Removed) {
54✔
454
            user = shared_from_this();
52✔
455
        }
52✔
456
        if (m_sync_manager) {
54✔
457
            app = m_sync_manager->app().lock();
54✔
458
        }
54✔
459
    }
54✔
460
    if (!user) {
54✔
461
        completion_block(app::AppError(
2✔
462
            ErrorCodes::ClientUserNotFound,
2✔
463
            util::format("Cannot initiate a refresh on user '%1' because the user has been removed", m_identity)));
2✔
464
    }
2✔
465
    else if (!app) {
52✔
466
        completion_block(app::AppError(
×
467
            ErrorCodes::ClientAppDeallocated,
×
468
            util::format("Cannot initiate a refresh on user '%1' because the app has been deallocated", m_identity)));
×
469
    }
×
470
    else {
52✔
471
        std::weak_ptr<SyncUser> weak_user = user->weak_from_this();
52✔
472
        app->refresh_custom_data(user, update_location,
52✔
473
                                 [completion_block = std::move(completion_block), weak_user](auto error) {
52✔
474
                                     if (auto strong = weak_user.lock()) {
52✔
475
                                         strong->emit_change_to_subscribers(*strong);
52✔
476
                                     }
52✔
477
                                     completion_block(error);
52✔
478
                                 });
52✔
479
    }
52✔
480
}
54✔
481

482
bool SyncUser::access_token_refresh_required() const
483
{
1,687✔
484
    using namespace std::chrono;
1,687✔
485
    constexpr size_t buffer_seconds = 5; // arbitrary
1,687✔
486
    util::CheckedLockGuard lock(m_tokens_mutex);
1,687✔
487
    const auto now = duration_cast<seconds>(system_clock::now().time_since_epoch()).count() +
1,687✔
488
                     m_seconds_to_adjust_time_for_testing.load(std::memory_order_relaxed);
1,687✔
489
    const auto threshold = now - buffer_seconds;
1,687✔
490
    return !m_access_token.token.empty() && m_access_token.expires_at < static_cast<int64_t>(threshold);
1,687✔
491
}
1,687✔
492

493
} // namespace realm
494

495
namespace std {
496
size_t hash<realm::SyncUserIdentity>::operator()(const realm::SyncUserIdentity& k) const
497
{
×
498
    return ((hash<string>()(k.id) ^ (hash<string>()(k.provider_type) << 1)) >> 1);
×
499
}
×
500
} // namespace std
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

© 2025 Coveralls, Inc