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

realm / realm-core / 2594

30 Aug 2024 02:48PM UTC coverage: 91.104% (-0.005%) from 91.109%
2594

push

Evergreen

web-flow
RCORE-2222 Remove 308 redirect tests (#7994)

* Removed redirect tests
* Removed one more location redirect test case

102824 of 181502 branches covered (56.65%)

157 of 161 new or added lines in 1 file covered. (97.52%)

61 existing lines in 21 files now uncovered.

217211 of 238421 relevant lines covered (91.1%)

5771330.6 hits per line

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

88.37
/src/realm/object-store/sync/app.cpp
1
////////////////////////////////////////////////////////////////////////////
2
//
3
// Copyright 2020 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 utilied.
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/app.hpp>
20

21
#include <realm/sync/network/http.hpp>
22
#include <realm/util/base64.hpp>
23
#include <realm/util/flat_map.hpp>
24
#include <realm/util/platform_info.hpp>
25
#include <realm/util/uri.hpp>
26
#include <realm/object-store/sync/app_user.hpp>
27
#include <realm/object-store/sync/app_utils.hpp>
28
#include <realm/object-store/sync/impl/app_metadata.hpp>
29
#include <realm/object-store/sync/impl/sync_file.hpp>
30
#include <realm/object-store/sync/sync_manager.hpp>
31

32
#ifdef __EMSCRIPTEN__
33
#include <realm/object-store/sync/impl/emscripten/network_transport.hpp>
34
#endif
35

36
#include <external/json/json.hpp>
37
#include <sstream>
38
#include <string>
39

40
using namespace realm;
41
using namespace realm::app;
42
using namespace bson;
43
using util::Optional;
44
using util::UniqueFunction;
45

46
namespace {
47
// MARK: - Helpers
48

49
REALM_COLD
50
REALM_NOINLINE
51
REALM_NORETURN
52
void throw_json_error(ErrorCodes::Error ec, std::string_view message)
53
{
2✔
54
    throw AppError(ec, std::string(message));
2✔
55
}
2✔
56

57
template <typename T>
58
T as(const Bson& bson)
59
{
67,240✔
60
    if (holds_alternative<T>(bson)) {
67,240✔
61
        return static_cast<T>(bson);
67,240✔
62
    }
67,240✔
63
    throw_json_error(ErrorCodes::MalformedJson, "?");
×
64
}
×
65

66
template <typename T>
67
T get(const BsonDocument& doc, const std::string& key)
68
{
47,989✔
69
    if (auto val = doc.find(key)) {
47,989✔
70
        return as<T>(*val);
47,989✔
71
    }
47,989✔
72
    throw_json_error(ErrorCodes::MissingJsonKey, key);
×
73
    return {};
×
74
}
47,989✔
75

76
template <typename T>
77
void read_field(const BsonDocument& data, const std::string& key, T& value)
78
{
64✔
79
    if (auto val = data.find(key)) {
64✔
80
        value = as<T>(*val);
64✔
81
    }
64✔
82
    else {
×
83
        throw_json_error(ErrorCodes::MissingJsonKey, key);
×
84
    }
×
85
}
64✔
86

87
template <>
88
void read_field(const BsonDocument& data, const std::string& key, ObjectId& value)
89
{
32✔
90
    value = ObjectId(get<std::string>(data, key).c_str());
32✔
91
}
32✔
92

93
template <typename T>
94
void read_field(const BsonDocument& data, const std::string& key, Optional<T>& value)
95
{
4,420✔
96
    if (auto val = data.find(key)) {
4,420✔
97
        value = as<T>(*val);
16✔
98
    }
16✔
99
}
4,420✔
100

101
template <typename T>
102
T parse(std::string_view str)
103
{
14,243✔
104
    try {
14,243✔
105
        return as<T>(bson::parse(str));
14,243✔
106
    }
14,243✔
107
    catch (const std::exception& e) {
14,243✔
108
        throw_json_error(ErrorCodes::MalformedJson, e.what());
2✔
109
    }
2✔
110
}
14,243✔
111

112
struct UserAPIKeyResponseHandler {
113
    UniqueFunction<void(App::UserAPIKey&&, Optional<AppError>)> completion;
114
    void operator()(const Response& response)
115
    {
44✔
116
        if (auto error = AppUtils::check_for_errors(response)) {
44✔
117
            return completion({}, std::move(error));
22✔
118
        }
22✔
119

120
        try {
22✔
121
            auto json = parse<BsonDocument>(response.body);
22✔
122
            completion(read_user_api_key(json), {});
22✔
123
        }
22✔
124
        catch (AppError& e) {
22✔
125
            completion({}, std::move(e));
×
126
        }
×
127
    }
22✔
128

129
    static App::UserAPIKey read_user_api_key(const BsonDocument& doc)
130
    {
32✔
131
        App::UserAPIKey user_api_key;
32✔
132
        read_field(doc, "_id", user_api_key.id);
32✔
133
        read_field(doc, "key", user_api_key.key);
32✔
134
        read_field(doc, "name", user_api_key.name);
32✔
135
        read_field(doc, "disabled", user_api_key.disabled);
32✔
136
        return user_api_key;
32✔
137
    }
32✔
138
};
139

140
// generate the request headers for a HTTP call, by default it will generate
141
// headers with a refresh token if a user is passed
142
HttpHeaders get_request_headers(const std::shared_ptr<User>& user, RequestTokenType token_type)
143
{
15,495✔
144
    HttpHeaders headers{{"Content-Type", "application/json;charset=utf-8"}, {"Accept", "application/json"}};
15,495✔
145
    if (user) {
15,495✔
146
        switch (token_type) {
5,741✔
147
            case RequestTokenType::NoAuth:
✔
148
                break;
×
149
            case RequestTokenType::AccessToken:
5,520✔
150
                headers.insert({"Authorization", util::format("Bearer %1", user->access_token())});
5,520✔
151
                break;
5,520✔
152
            case RequestTokenType::RefreshToken:
221✔
153
                headers.insert({"Authorization", util::format("Bearer %1", user->refresh_token())});
221✔
154
                break;
221✔
155
        }
5,741✔
156
    }
5,741✔
157
    return headers;
15,495✔
158
}
15,495✔
159

160
std::string trim_base_url(std::string base_url)
161
{
4,989✔
162
    while (!base_url.empty() && base_url.back() == '/') {
4,991✔
163
        base_url.pop_back();
2✔
164
    }
2✔
165

166
    return base_url;
4,989✔
167
}
4,989✔
168

169
std::string base_url_from_app_config(const AppConfig& app_config)
170
{
4,407✔
171
    if (!app_config.base_url) {
4,407✔
172
        return std::string{App::default_base_url()};
3,808✔
173
    }
3,808✔
174

175
    return trim_base_url(*app_config.base_url);
599✔
176
}
4,407✔
177

178
UniqueFunction<void(const Response&)> handle_default_response(UniqueFunction<void(Optional<AppError>)>&& completion)
179
{
24✔
180
    return [completion = std::move(completion)](const Response& response) {
24✔
181
        completion(AppUtils::check_for_errors(response));
24✔
182
    };
24✔
183
}
24✔
184

185
constexpr static std::string_view s_base_path = "/api/client/v2.0";
186
constexpr static std::string_view s_app_path = "/app";
187
constexpr static std::string_view s_auth_path = "/auth";
188
constexpr static std::string_view s_sync_path = "/realm-sync";
189
constexpr static uint64_t s_default_timeout_ms = 60000;
190
constexpr static std::string_view s_username_password_provider_key = "local-userpass";
191
constexpr static std::string_view s_user_api_key_provider_key_path = "api_keys";
192
constexpr static int s_max_http_redirects = 20;
193
static util::FlatMap<std::string, util::FlatMap<std::string, SharedApp>> s_apps_cache; // app_id -> base_url -> app
194
std::mutex s_apps_mutex;
195
} // anonymous namespace
196

197
namespace realm::app {
198

199
std::string_view App::default_base_url()
200
{
3,890✔
201
    return "https://services.cloud.mongodb.com";
3,890✔
202
}
3,890✔
203

204
// NO_THREAD_SAFETY_ANALYSIS because clang generates a false positive.
205
// "Calling function configure requires negative capability '!app->m_route_mutex'"
206
// But 'app' is an object just created in this static method so it is not possible to annotate this in the header.
207
SharedApp App::get_app(CacheMode mode, const AppConfig& config) NO_THREAD_SAFETY_ANALYSIS
208
{
4,401✔
209
    if (mode == CacheMode::Enabled) {
4,401✔
210
        std::lock_guard lock(s_apps_mutex);
10✔
211
        auto& app = s_apps_cache[config.app_id][base_url_from_app_config(config)];
10✔
212
        if (!app) {
10✔
213
            app = App::make_app(config);
6✔
214
        }
6✔
215
        return app;
10✔
216
    }
10✔
217
    REALM_ASSERT(mode == CacheMode::Disabled);
4,391✔
218
    return App::make_app(config);
4,391✔
219
}
4,401✔
220

221
SharedApp App::make_app(const AppConfig& config)
222
{
4,397✔
223
#ifdef __EMSCRIPTEN__
224
    if (!config.transport) {
225
        // Make a copy and provide a default transport if not provided
226
        AppConfig config_copy = config;
227
        config_copy.transport = std::make_shared<_impl::EmscriptenNetworkTransport>();
228
        return std::make_shared<App>(Private(), config_copy);
229
    }
230
    return std::make_shared<App>(Private(), config);
231
#else
232
    return std::make_shared<App>(Private(), config);
4,397✔
233
#endif
4,397✔
234
}
4,397✔
235

236
SharedApp App::get_cached_app(const std::string& app_id, const std::optional<std::string>& base_url)
237
{
10✔
238
    std::lock_guard lock(s_apps_mutex);
10✔
239
    if (auto it = s_apps_cache.find(app_id); it != s_apps_cache.end()) {
10✔
240
        const auto& apps_by_url = it->second;
10✔
241

242
        auto app_it = base_url ? apps_by_url.find(trim_base_url(*base_url)) : apps_by_url.begin();
10✔
243
        if (app_it != apps_by_url.end()) {
10✔
244
            return app_it->second;
8✔
245
        }
8✔
246
    }
10✔
247

248
    return nullptr;
2✔
249
}
10✔
250

251
void App::clear_cached_apps()
252
{
4,573✔
253
    std::lock_guard lock(s_apps_mutex);
4,573✔
254
    s_apps_cache.clear();
4,573✔
255
}
4,573✔
256

257
void App::close_all_sync_sessions()
258
{
×
259
    std::lock_guard lock(s_apps_mutex);
×
260
    for (auto& apps_by_url : s_apps_cache) {
×
261
        for (auto& app : apps_by_url.second) {
×
262
            app.second->sync_manager()->close_all_sessions();
×
263
        }
×
264
    }
×
265
}
×
266

267
App::App(Private, const AppConfig& config)
268
    : m_config(config)
2,190✔
269
    , m_base_url(base_url_from_app_config(m_config))
2,190✔
270
    , m_request_timeout_ms(m_config.default_request_timeout_ms.value_or(s_default_timeout_ms))
2,190✔
271
    , m_file_manager(std::make_unique<SyncFileManager>(config))
2,190✔
272
    , m_metadata_store(create_metadata_store(config, *m_file_manager))
2,190✔
273
    , m_sync_manager(SyncManager::create(config.sync_client_config))
2,190✔
274
{
4,397✔
275
    REALM_ASSERT(m_config.transport);
4,397✔
276

277
    // if a base url is provided, then verify the value
278
    if (m_config.base_url) {
4,397✔
279
        util::Uri::parse(*m_config.base_url);
595✔
280
    }
595✔
281
    // Setup a baseline set of routes using the provided or default base url
282
    // These will be updated when the location info is refreshed prior to sending the
283
    // first AppServices HTTP request.
284
    configure_route(m_base_url, "");
4,397✔
285
    m_sync_manager->set_sync_route(make_sync_route(), false);
4,397✔
286

287
    if (m_config.device_info.platform_version.empty()) {
4,397✔
288
        throw InvalidArgument("You must specify the Platform Version in App::Config::device_info");
×
289
    }
×
290

291
    if (m_config.device_info.sdk.empty()) {
4,397✔
292
        throw InvalidArgument("You must specify the SDK Name in App::Config::device_info");
×
293
    }
×
294

295
    if (m_config.device_info.sdk_version.empty()) {
4,397✔
296
        throw InvalidArgument("You must specify the SDK Version in App::Config::device_info");
×
297
    }
×
298
}
4,397✔
299

300
App::~App() {}
4,397✔
301

302
bool App::init_logger()
303
{
46,536✔
304
    if (!m_logger_ptr) {
46,536✔
305
        m_logger_ptr = m_sync_manager->get_logger();
4,389✔
306
        if (!m_logger_ptr) {
4,389✔
307
            m_logger_ptr = util::Logger::get_default_logger();
×
308
        }
×
309
    }
4,389✔
310
    return bool(m_logger_ptr);
46,536✔
311
}
46,536✔
312

313
bool App::would_log(util::Logger::Level level)
314
{
6,106✔
315
    return init_logger() && m_logger_ptr->would_log(util::LogCategory::app, level);
6,106✔
316
}
6,106✔
317

318
template <class... Params>
319
void App::log_debug(const char* message, Params&&... params)
320
{
40,298✔
321
    if (init_logger()) {
40,298✔
322
        m_logger_ptr->log(util::LogCategory::app, util::Logger::Level::debug, message,
40,298✔
323
                          std::forward<Params>(params)...);
40,298✔
324
    }
40,298✔
325
}
40,298✔
326

327
template <class... Params>
328
void App::log_error(const char* message, Params&&... params)
329
{
132✔
330
    if (init_logger()) {
132✔
331
        m_logger_ptr->log(util::LogCategory::app, util::Logger::Level::error, message,
132✔
332
                          std::forward<Params>(params)...);
132✔
333
    }
132✔
334
}
132✔
335

336
std::string App::auth_route()
337
{
9,744✔
338
    util::CheckedLockGuard guard(m_route_mutex);
9,744✔
339
    return m_auth_route;
9,744✔
340
}
9,744✔
341

342
std::string App::base_url()
343
{
×
344
    util::CheckedLockGuard guard(m_route_mutex);
×
345
    return m_base_url;
×
346
}
×
347

348
std::string App::get_host_url()
349
{
4,450✔
350
    util::CheckedLockGuard guard(m_route_mutex);
4,450✔
351
    return m_host_url;
4,450✔
352
}
4,450✔
353

354
std::string App::get_ws_host_url()
355
{
72✔
356
    util::CheckedLockGuard guard(m_route_mutex);
72✔
357
    return m_ws_host_url;
72✔
358
}
72✔
359

360
std::string App::make_sync_route(Optional<std::string> ws_host_url)
361
{
8,777✔
362
    return util::format("%1%2%3/%4%5", ws_host_url.value_or(m_ws_host_url), s_base_path, s_app_path, m_config.app_id,
8,777✔
363
                        s_sync_path);
8,777✔
364
}
8,777✔
365

366
void App::configure_route(const std::string& host_url, const std::string& ws_host_url)
367
{
8,785✔
368
    m_host_url = host_url;
8,785✔
369
    m_ws_host_url = ws_host_url;
8,785✔
370
    if (m_ws_host_url.empty())
8,785✔
371
        m_ws_host_url = App::create_ws_host_url(m_host_url);
4,397✔
372

373
    // host_url is the url to the server: e.g., https://services.cloud.mongodb.com or https://localhost:9090
374
    // base_route is the baseline client api path: e.g. <host_url>/api/client/v2.0
375
    m_base_route = util::format("%1%2", m_host_url, s_base_path);
8,785✔
376
    // app_route is the cloud app URL: <host_url>/api/client/v2.0/app/<app_id>
377
    m_app_route = util::format("%1%2/%3", m_base_route, s_app_path, m_config.app_id);
8,785✔
378
    // auth_route is cloud app auth URL: <host_url>/api/client/v2.0/app/<app_id>/auth
379
    m_auth_route = util::format("%1%2", m_app_route, s_auth_path);
8,785✔
380
}
8,785✔
381

382
// Create a temporary websocket URL domain using the given host URL
383
// This updates the URL based on the following assumptions:
384
// If the URL doesn't start with 'http' => <host-url>
385
// http[s]://[region-prefix]realm.mongodb.com => ws[s]://ws.[region-prefix]realm.mongodb.com
386
// http[s]://[region-prefix]services.cloud.mongodb.com => ws[s]://[region-prefix].ws.services.cloud.mongodb.com
387
// All others => http[s]://<host-url> => ws[s]://<host-url>
388
std::string App::create_ws_host_url(std::string_view host_url)
389
{
4,591✔
390
    constexpr static std::string_view old_base_domain = "realm.mongodb.com";
4,591✔
391
    constexpr static std::string_view new_base_domain = "services.cloud.mongodb.com";
4,591✔
392
    const size_t base_len = std::char_traits<char>::length("http://");
4,591✔
393

394
    // Doesn't contain 7 or more characters (length of 'http://') or start with http,
395
    // just return provided string
396
    if (host_url.length() < base_len || host_url.substr(0, 4) != "http") {
4,591✔
397
        return std::string(host_url);
2✔
398
    }
2✔
399
    // If it starts with 'https' then ws url will start with 'wss'
400
    bool https = host_url[4] == 's';
4,589✔
401
    size_t prefix_len = base_len + (https ? 1 : 0);
4,589✔
402
    std::string_view prefix = https ? "wss://" : "ws://";
4,589✔
403

404
    // http[s]://[<region-prefix>]realm.mongodb.com[/<path>] =>
405
    //     ws[s]://ws.[<region-prefix>]realm.mongodb.com[/<path>]
406
    if (host_url.find(old_base_domain) != std::string_view::npos) {
4,589✔
407
        return util::format("%1ws.%2", prefix, host_url.substr(prefix_len));
12✔
408
    }
12✔
409
    // http[s]://[<region-prefix>]services.cloud.mongodb.com[/<path>] =>
410
    //     ws[s]://[<region-prefix>].ws.services.cloud.mongodb.com[/<path>]
411
    if (auto start = host_url.find(new_base_domain); start != std::string_view::npos) {
4,577✔
412
        return util::format("%1%2ws.%3", prefix, host_url.substr(prefix_len, start - prefix_len),
3,850✔
413
                            host_url.substr(start));
3,850✔
414
    }
3,850✔
415

416
    // All others => http[s]://<host-url>[/<path>] => ws[s]://<host-url>[/<path>]
417
    return util::format("ws%1", host_url.substr(4));
727✔
418
}
4,577✔
419

420
void App::update_hostname(const std::string& host_url, const std::string& ws_host_url,
421
                          const std::string& new_base_url)
422
{
4,388✔
423
    log_debug("App: update_hostname: %1 | %2 | %3", host_url, ws_host_url, new_base_url);
4,388✔
424
    m_base_url = trim_base_url(new_base_url);
4,388✔
425
    // If a new host url was returned from the server, use it to configure the routes
426
    // Otherwise, use the m_base_url value
427
    std::string base_url = host_url.length() > 0 ? host_url : m_base_url;
4,388✔
428
    configure_route(base_url, ws_host_url);
4,388✔
429
}
4,388✔
430

431
// MARK: - Template specializations
432

433
template <>
434
App::UsernamePasswordProviderClient App::provider_client<App::UsernamePasswordProviderClient>()
435
{
4,816✔
436
    return App::UsernamePasswordProviderClient(shared_from_this());
4,816✔
437
}
4,816✔
438

439
template <>
440
App::UserAPIKeyProviderClient App::provider_client<App::UserAPIKeyProviderClient>()
441
{
20✔
442
    return App::UserAPIKeyProviderClient(*this);
20✔
443
}
20✔
444

445
// MARK: - UsernamePasswordProviderClient
446

447
void App::UsernamePasswordProviderClient::register_email(const std::string& email, const std::string& password,
448
                                                         UniqueFunction<void(Optional<AppError>)>&& completion)
449
{
4,820✔
450
    m_parent->log_debug("App: register_email: %1", email);
4,820✔
451
    m_parent->post(util::format("%1/providers/%2/register", m_parent->auth_route(), s_username_password_provider_key),
4,820✔
452
                   std::move(completion), {{"email", email}, {"password", password}});
4,820✔
453
}
4,820✔
454

455
void App::UsernamePasswordProviderClient::confirm_user(const std::string& token, const std::string& token_id,
456
                                                       UniqueFunction<void(Optional<AppError>)>&& completion)
457
{
2✔
458
    m_parent->log_debug("App: confirm_user");
2✔
459
    m_parent->post(util::format("%1/providers/%2/confirm", m_parent->auth_route(), s_username_password_provider_key),
2✔
460
                   std::move(completion), {{"token", token}, {"tokenId", token_id}});
2✔
461
}
2✔
462

463
void App::UsernamePasswordProviderClient::resend_confirmation_email(
464
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
465
{
2✔
466
    m_parent->log_debug("App: resend_confirmation_email: %1", email);
2✔
467
    m_parent->post(
2✔
468
        util::format("%1/providers/%2/confirm/send", m_parent->auth_route(), s_username_password_provider_key),
2✔
469
        std::move(completion), {{"email", email}});
2✔
470
}
2✔
471

472
void App::UsernamePasswordProviderClient::retry_custom_confirmation(
473
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
474
{
4✔
475
    m_parent->log_debug("App: retry_custom_confirmation: %1", email);
4✔
476
    m_parent->post(
4✔
477
        util::format("%1/providers/%2/confirm/call", m_parent->auth_route(), s_username_password_provider_key),
4✔
478
        std::move(completion), {{"email", email}});
4✔
479
}
4✔
480

481
void App::UsernamePasswordProviderClient::send_reset_password_email(
482
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
483
{
×
484
    m_parent->log_debug("App: send_reset_password_email: %1", email);
×
485
    m_parent->post(
×
486
        util::format("%1/providers/%2/reset/send", m_parent->auth_route(), s_username_password_provider_key),
×
487
        std::move(completion), {{"email", email}});
×
488
}
×
489

490
void App::UsernamePasswordProviderClient::reset_password(const std::string& password, const std::string& token,
491
                                                         const std::string& token_id,
492
                                                         UniqueFunction<void(Optional<AppError>)>&& completion)
493
{
2✔
494
    m_parent->log_debug("App: reset_password");
2✔
495
    m_parent->post(util::format("%1/providers/%2/reset", m_parent->auth_route(), s_username_password_provider_key),
2✔
496
                   std::move(completion), {{"password", password}, {"token", token}, {"tokenId", token_id}});
2✔
497
}
2✔
498

499
void App::UsernamePasswordProviderClient::call_reset_password_function(
500
    const std::string& email, const std::string& password, const BsonArray& args,
501
    UniqueFunction<void(Optional<AppError>)>&& completion)
502
{
6✔
503
    m_parent->log_debug("App: call_reset_password_function: %1", email);
6✔
504
    m_parent->post(
6✔
505
        util::format("%1/providers/%2/reset/call", m_parent->auth_route(), s_username_password_provider_key),
6✔
506
        std::move(completion), {{"email", email}, {"password", password}, {"arguments", args}});
6✔
507
}
6✔
508

509
// MARK: - UserAPIKeyProviderClient
510

511
std::string App::UserAPIKeyProviderClient::url_for_path(const std::string& path = "") const
512
{
82✔
513
    if (!path.empty()) {
82✔
514
        return m_auth_request_client.url_for_path(
58✔
515
            util::format("%1/%2/%3", s_auth_path, s_user_api_key_provider_key_path, path));
58✔
516
    }
58✔
517

518
    return m_auth_request_client.url_for_path(util::format("%1/%2", s_auth_path, s_user_api_key_provider_key_path));
24✔
519
}
82✔
520

521
void App::UserAPIKeyProviderClient::create_api_key(
522
    const std::string& name, const std::shared_ptr<User>& user,
523
    UniqueFunction<void(UserAPIKey&&, Optional<AppError>)>&& completion)
524
{
10✔
525
    m_auth_request_client.do_authenticated_request(
10✔
526
        HttpMethod::post, url_for_path(), Bson(BsonDocument{{"name", name}}).to_string(), user,
10✔
527
        RequestTokenType::RefreshToken, UserAPIKeyResponseHandler{std::move(completion)});
10✔
528
}
10✔
529

530
void App::UserAPIKeyProviderClient::fetch_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
531
                                                  UniqueFunction<void(UserAPIKey&&, Optional<AppError>)>&& completion)
532
{
34✔
533
    m_auth_request_client.do_authenticated_request(HttpMethod::get, url_for_path(id.to_string()), "", user,
34✔
534
                                                   RequestTokenType::RefreshToken,
34✔
535
                                                   UserAPIKeyResponseHandler{std::move(completion)});
34✔
536
}
34✔
537

538
void App::UserAPIKeyProviderClient::fetch_api_keys(
539
    const std::shared_ptr<User>& user,
540
    UniqueFunction<void(std::vector<UserAPIKey>&&, Optional<AppError>)>&& completion)
541
{
14✔
542
    m_auth_request_client.do_authenticated_request(
14✔
543
        HttpMethod::get, url_for_path(), "", user, RequestTokenType::RefreshToken,
14✔
544
        [completion = std::move(completion)](const Response& response) {
14✔
545
            if (auto error = AppUtils::check_for_errors(response)) {
14✔
546
                return completion({}, std::move(error));
4✔
547
            }
4✔
548

549
            try {
10✔
550
                auto json = parse<BsonArray>(response.body);
10✔
551
                std::vector<UserAPIKey> keys;
10✔
552
                keys.reserve(json.size());
10✔
553
                for (auto&& api_key_json : json) {
10✔
554
                    keys.push_back(UserAPIKeyResponseHandler::read_user_api_key(as<BsonDocument>(api_key_json)));
10✔
555
                }
10✔
556
                return completion(std::move(keys), {});
10✔
557
            }
10✔
558
            catch (AppError& e) {
10✔
559
                completion({}, std::move(e));
×
560
            }
×
561
        });
10✔
562
}
14✔
563

564
void App::UserAPIKeyProviderClient::delete_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
565
                                                   UniqueFunction<void(Optional<AppError>)>&& completion)
566
{
8✔
567
    m_auth_request_client.do_authenticated_request(HttpMethod::del, url_for_path(id.to_string()), "", user,
8✔
568
                                                   RequestTokenType::RefreshToken,
8✔
569
                                                   handle_default_response(std::move(completion)));
8✔
570
}
8✔
571

572
void App::UserAPIKeyProviderClient::enable_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
573
                                                   UniqueFunction<void(Optional<AppError>)>&& completion)
574
{
8✔
575
    m_auth_request_client.do_authenticated_request(
8✔
576
        HttpMethod::put, url_for_path(util::format("%1/enable", id.to_string())), "", user,
8✔
577
        RequestTokenType::RefreshToken, handle_default_response(std::move(completion)));
8✔
578
}
8✔
579

580
void App::UserAPIKeyProviderClient::disable_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
581
                                                    UniqueFunction<void(Optional<AppError>)>&& completion)
582
{
8✔
583
    m_auth_request_client.do_authenticated_request(
8✔
584
        HttpMethod::put, url_for_path(util::format("%1/disable", id.to_string())), "", user,
8✔
585
        RequestTokenType::RefreshToken, handle_default_response(std::move(completion)));
8✔
586
}
8✔
587
// MARK: - App
588

589
// The user cache can have an expired pointer to an object if another thread is
590
// currently waiting for the mutex so that it can unregister the object, which
591
// will result in shared_from_this() throwing. We could instead do
592
// `weak_from_this().lock()`, but that is more expensive in the much more common
593
// case where the pointer is valid.
594
//
595
// Storing weak_ptrs in m_user would also avoid this problem, but would introduce
596
// a different one where the natural way to use the users could result in us
597
// trying to release the final strong reference while holding the lock, which
598
// would lead to a deadlock
599
static std::shared_ptr<User> try_lock(User& user)
600
{
3,917✔
601
    try {
3,917✔
602
        return user.shared_from_this();
3,917✔
603
    }
3,917✔
604
    catch (const std::bad_weak_ptr&) {
3,917✔
605
        return nullptr;
×
606
    }
×
607
}
3,917✔
608

609
std::shared_ptr<User> App::get_user_for_id(const std::string& user_id)
610
{
9,645✔
611
    if (auto& user = m_users[user_id]) {
9,645✔
612
        if (auto locked = try_lock(*user)) {
52✔
613
            return locked;
52✔
614
        }
52✔
615
    }
52✔
616
    return User::make(shared_from_this(), user_id);
9,593✔
617
}
9,645✔
618

619
void App::user_data_updated(const std::string& user_id)
620
{
4,878✔
621
    if (auto it = m_users.find(user_id); it != m_users.end()) {
4,878✔
622
        it->second->update_backing_data(m_metadata_store->get_user(user_id));
6✔
623
    }
6✔
624
}
4,878✔
625

626
std::shared_ptr<User> App::current_user()
627
{
8,614✔
628
    util::CheckedLockGuard lock(m_user_mutex);
8,614✔
629
    if (m_current_user && m_current_user->is_logged_in()) {
8,614✔
630
        if (auto user = try_lock(*m_current_user)) {
3,857✔
631
            return user;
3,857✔
632
        }
3,857✔
633
    }
3,857✔
634
    if (auto user_id = m_metadata_store->get_current_user(); !user_id.empty()) {
4,757✔
635
        auto user = get_user_for_id(user_id);
4,715✔
636
        m_current_user = user.get();
4,715✔
637
        return user;
4,715✔
638
    }
4,715✔
639
    return nullptr;
42✔
640
}
4,757✔
641

642
std::shared_ptr<User> App::get_existing_logged_in_user(std::string_view user_id)
643
{
2✔
644
    util::CheckedLockGuard lock(m_user_mutex);
2✔
645
    if (auto it = m_users.find(std::string(user_id)); it != m_users.end() && it->second->is_logged_in()) {
2!
646
        if (auto user = try_lock(*it->second)) {
×
647
            return user;
×
648
        }
×
649
    }
×
650
    if (m_metadata_store->has_logged_in_user(user_id)) {
2✔
651
        return User::make(shared_from_this(), user_id);
2✔
652
    }
2✔
653
    return nullptr;
×
654
}
2✔
655

656
std::string App::get_base_url() const
657
{
74✔
658
    util::CheckedLockGuard guard(m_route_mutex);
74✔
659
    return m_base_url;
74✔
660
}
74✔
661

662
void App::update_base_url(std::string_view new_base_url, UniqueFunction<void(Optional<AppError>)>&& completion)
663
{
14✔
664
    if (new_base_url.empty()) {
14✔
665
        // Treat an empty string the same as requesting the default base url
666
        new_base_url = App::default_base_url();
12✔
667
        log_debug("App::update_base_url: empty => %1", new_base_url);
12✔
668
    }
12✔
669
    else {
2✔
670
        log_debug("App::update_base_url: %1", new_base_url);
2✔
671
    }
2✔
672

673
    // Validate the new base_url
674
    util::Uri::parse(new_base_url);
14✔
675

676
    bool update_not_needed;
14✔
677
    {
14✔
678
        util::CheckedLockGuard guard(m_route_mutex);
14✔
679
        // Update the location if the base_url is different or a location update is already needed
680
        m_location_updated = (new_base_url == m_base_url) && m_location_updated;
14!
681
        update_not_needed = m_location_updated;
14✔
682
    }
14✔
683
    // If the new base_url is the same as the current base_url and the location has already been updated,
684
    // then we're done
685
    if (update_not_needed) {
14✔
686
        completion(util::none);
×
687
        return;
×
688
    }
×
689

690
    // Otherwise, request the location information at the new base URL
691
    request_location(std::move(completion), std::string(new_base_url));
14✔
692
}
14✔
693

694
std::vector<std::shared_ptr<User>> App::all_users()
695
{
82✔
696
    util::CheckedLockGuard lock(m_user_mutex);
82✔
697
    auto user_ids = m_metadata_store->get_all_users();
82✔
698
    std::vector<std::shared_ptr<User>> users;
82✔
699
    users.reserve(user_ids.size());
82✔
700
    for (auto& user_id : user_ids) {
82✔
701
        users.push_back(get_user_for_id(user_id));
52✔
702
    }
52✔
703
    return users;
82✔
704
}
82✔
705

706
void App::get_profile(const std::shared_ptr<User>& user,
707
                      UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
708
{
4,886✔
709
    do_authenticated_request(
4,886✔
710
        HttpMethod::get, url_for_path("/auth/profile"), "", user, RequestTokenType::AccessToken,
4,886✔
711
        [completion = std::move(completion), self = shared_from_this(), user,
4,886✔
712
         this](const Response& profile_response) {
4,886✔
713
            if (auto error = AppUtils::check_for_errors(profile_response)) {
4,886✔
714
                return completion(nullptr, std::move(error));
×
715
            }
×
716

717
            try {
4,886✔
718
                auto profile_json = parse<BsonDocument>(profile_response.body);
4,886✔
719
                auto identities_json = get<BsonArray>(profile_json, "identities");
4,886✔
720

721
                std::vector<UserIdentity> identities;
4,886✔
722
                identities.reserve(identities_json.size());
4,886✔
723
                for (auto& identity_json : identities_json) {
4,920✔
724
                    auto doc = as<BsonDocument>(identity_json);
4,920✔
725
                    identities.push_back({get<std::string>(doc, "id"), get<std::string>(doc, "provider_type")});
4,920✔
726
                }
4,920✔
727

728
                m_metadata_store->update_user(user->user_id(), [&](auto& data) {
4,886✔
729
                    data.identities = std::move(identities);
4,886✔
730
                    data.profile = UserProfile(get<BsonDocument>(profile_json, "data"));
4,886✔
731
                    user->update_backing_data(data); // FIXME
4,886✔
732
                });
4,886✔
733
            }
4,886✔
734
            catch (const AppError& err) {
4,886✔
735
                return completion(nullptr, err);
×
736
            }
×
737

738
            return completion(user, {});
4,886✔
739
        });
4,886✔
740
}
4,886✔
741

742
void App::attach_auth_options(BsonDocument& body)
743
{
4,908✔
744
    log_debug("App: version info: platform: %1  version: %2 - sdk: %3 - sdk version: %4 - core version: %5",
4,908✔
745
              util::get_library_platform(), m_config.device_info.platform_version, m_config.device_info.sdk,
4,908✔
746
              m_config.device_info.sdk_version, REALM_VERSION_STRING);
4,908✔
747

748
    BsonDocument options;
4,908✔
749
    options["appId"] = m_config.app_id;
4,908✔
750
    options["platform"] = util::get_library_platform();
4,908✔
751
    options["platformVersion"] = m_config.device_info.platform_version;
4,908✔
752
    options["sdk"] = m_config.device_info.sdk;
4,908✔
753
    options["sdkVersion"] = m_config.device_info.sdk_version;
4,908✔
754
    options["cpuArch"] = util::get_library_cpu_arch();
4,908✔
755
    options["deviceName"] = m_config.device_info.device_name;
4,908✔
756
    options["deviceVersion"] = m_config.device_info.device_version;
4,908✔
757
    options["frameworkName"] = m_config.device_info.framework_name;
4,908✔
758
    options["frameworkVersion"] = m_config.device_info.framework_version;
4,908✔
759
    options["coreVersion"] = REALM_VERSION_STRING;
4,908✔
760
    options["bundleId"] = m_config.device_info.bundle_id;
4,908✔
761

762
    body["options"] = BsonDocument({{"device", options}});
4,908✔
763
}
4,908✔
764

765
void App::log_in_with_credentials(const AppCredentials& credentials, const std::shared_ptr<User>& linking_user,
766
                                  UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
767
{
4,916✔
768
    if (would_log(util::Logger::Level::debug)) {
4,916✔
769
        auto app_info = util::format("app_id: %1", m_config.app_id);
4,916✔
770
        log_debug("App: log_in_with_credentials: %1", app_info);
4,916✔
771
    }
4,916✔
772
    // if we try logging in with an anonymous user while there
773
    // is already an anonymous session active, reuse it
774
    std::shared_ptr<User> anon_user;
4,916✔
775
    if (credentials.provider() == AuthProvider::ANONYMOUS) {
4,916✔
776
        util::CheckedLockGuard lock(m_user_mutex);
86✔
777
        for (auto& [_, user] : m_users) {
86✔
778
            if (user->is_anonymous()) {
14✔
779
                anon_user = try_lock(*user);
8✔
780
                if (!anon_user)
8✔
781
                    continue;
×
782
                m_current_user = user;
8✔
783
                m_metadata_store->set_current_user(user->user_id());
8✔
784
                break;
8✔
785
            }
8✔
786
        }
14✔
787
    }
86✔
788

789
    if (anon_user) {
4,916✔
790
        emit_change_to_subscribers();
8✔
791
        completion(anon_user, util::none);
8✔
792
        return;
8✔
793
    }
8✔
794

795
    if (linking_user) {
4,908✔
796
        util::CheckedLockGuard lock(m_user_mutex);
10✔
797
        if (!verify_user_present(linking_user)) {
10✔
798
            return completion(nullptr, AppError(ErrorCodes::ClientUserNotFound, "The specified user was not found."));
×
799
        }
×
800
    }
10✔
801

802
    // construct the route
803
    std::string route = util::format("%1/providers/%2/login%3", auth_route(), credentials.provider_as_string(),
4,908✔
804
                                     linking_user ? "?link=true" : "");
4,908✔
805

806
    BsonDocument body = credentials.serialize_as_bson();
4,908✔
807
    attach_auth_options(body);
4,908✔
808

809
    do_request(
4,908✔
810
        make_request(HttpMethod::post, std::move(route), linking_user, RequestTokenType::AccessToken,
4,908✔
811
                     Bson(body).to_string()),
4,908✔
812
        [completion = std::move(completion), credentials, linking_user, self = shared_from_this(),
4,908✔
813
         this](auto&&, const Response& response) mutable {
4,908✔
814
            if (auto error = AppUtils::check_for_errors(response)) {
4,908✔
815
                log_error("App: log_in_with_credentials failed: %1 message: %2", response.http_status_code,
18✔
816
                          error->what());
18✔
817
                return completion(nullptr, std::move(error));
18✔
818
            }
18✔
819

820
            std::shared_ptr<User> user = linking_user;
4,890✔
821
            try {
4,890✔
822
                auto json = parse<BsonDocument>(response.body);
4,890✔
823
                if (linking_user) {
4,890✔
824
                    m_metadata_store->update_user(linking_user->user_id(), [&](auto& data) {
10✔
825
                        data.access_token = RealmJWT(get<std::string>(json, "access_token"));
10✔
826
                        // FIXME: should be powered by callback
827
                        linking_user->update_backing_data(data);
10✔
828
                    });
10✔
829
                }
10✔
830
                else {
4,880✔
831
                    auto user_id = get<std::string>(json, "user_id");
4,880✔
832
                    m_metadata_store->create_user(user_id, get<std::string>(json, "refresh_token"),
4,880✔
833
                                                  get<std::string>(json, "access_token"),
4,880✔
834
                                                  get<std::string>(json, "device_id"));
4,880✔
835
                    util::CheckedLockGuard lock(m_user_mutex);
4,880✔
836
                    user_data_updated(user_id); // FIXME: needs to be callback from metadata store
4,880✔
837
                    user = get_user_for_id(user_id);
4,880✔
838
                }
4,880✔
839
            }
4,890✔
840
            catch (const AppError& e) {
4,890✔
841
                return completion(nullptr, e);
2✔
842
            }
2✔
843
            // If the user has not been logged in, then there is a problem with the token
844
            if (!user->is_logged_in()) {
4,888✔
845
                return completion(nullptr,
2✔
846
                                  AppError(ErrorCodes::BadToken, "Could not log in user: received malformed JWT"));
2✔
847
            }
2✔
848

849
            get_profile(user, [this, completion = std::move(completion)](const std::shared_ptr<User>& user,
4,886✔
850
                                                                         Optional<AppError> error) {
4,886✔
851
                if (!error) {
4,886✔
852
                    switch_user(user);
4,886✔
853
                }
4,886✔
854
                completion(user, error);
4,886✔
855
            });
4,886✔
856
        },
4,886✔
857
        false);
4,908✔
858
}
4,908✔
859

860
void App::log_in_with_credentials(
861
    const AppCredentials& credentials,
862
    util::UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
863
{
4,906✔
864
    App::log_in_with_credentials(credentials, nullptr, std::move(completion));
4,906✔
865
}
4,906✔
866

867
void App::log_out(const std::shared_ptr<User>& user, SyncUser::State new_state,
868
                  UniqueFunction<void(Optional<AppError>)>&& completion)
869
{
52✔
870
    if (!user || user->state() == new_state || user->state() == SyncUser::State::Removed) {
52✔
871
        if (completion) {
6✔
872
            completion(util::none);
4✔
873
        }
4✔
874
        return;
6✔
875
    }
6✔
876

877
    log_debug("App: log_out(%1)", user->user_id());
46✔
878
    auto request =
46✔
879
        make_request(HttpMethod::del, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, "");
46✔
880

881
    m_metadata_store->log_out(user->user_id(), new_state);
46✔
882
    user->update_backing_data(m_metadata_store->get_user(user->user_id()));
46✔
883

884
    do_request(std::move(request),
46✔
885
               [self = shared_from_this(), completion = std::move(completion)](auto&&, const Response& response) {
46✔
886
                   auto error = AppUtils::check_for_errors(response);
46✔
887
                   if (!error) {
46✔
888
                       self->emit_change_to_subscribers();
46✔
889
                   }
46✔
890
                   if (completion) {
46✔
891
                       completion(error);
41✔
892
                   }
41✔
893
               });
46✔
894
}
46✔
895

896
void App::log_out(const std::shared_ptr<User>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
897
{
37✔
898
    auto new_state = user && user->is_anonymous() ? SyncUser::State::Removed : SyncUser::State::LoggedOut;
37✔
899
    log_out(user, new_state, std::move(completion));
37✔
900
}
37✔
901

902
void App::log_out(UniqueFunction<void(Optional<AppError>)>&& completion)
903
{
18✔
904
    log_out(current_user(), std::move(completion));
18✔
905
}
18✔
906

907
bool App::verify_user_present(const std::shared_ptr<User>& user) const
908
{
4,927✔
909
    for (auto& [_, u] : m_users) {
4,929✔
910
        if (u == user.get())
4,929✔
911
            return true;
4,927✔
912
    }
4,929✔
913
    return false;
×
914
}
4,927✔
915

916
void App::switch_user(const std::shared_ptr<User>& user)
917
{
4,894✔
918
    if (!user || user->state() != SyncUser::State::LoggedIn) {
4,894✔
919
        throw AppError(ErrorCodes::ClientUserNotLoggedIn, "User is no longer valid or is logged out");
2✔
920
    }
2✔
921
    {
4,892✔
922
        util::CheckedLockGuard lock(m_user_mutex);
4,892✔
923
        if (!verify_user_present(user)) {
4,892✔
924
            throw AppError(ErrorCodes::ClientUserNotFound, "User does not exist");
×
925
        }
×
926

927
        m_current_user = user.get();
4,892✔
928
        m_metadata_store->set_current_user(user->user_id());
4,892✔
929
    }
4,892✔
930
    emit_change_to_subscribers();
×
931
}
4,892✔
932

933
void App::remove_user(const std::shared_ptr<User>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
934
{
21✔
935
    if (!user || user->state() == SyncUser::State::Removed) {
21✔
936
        return completion(AppError(ErrorCodes::ClientUserNotFound, "User has already been removed"));
4✔
937
    }
4✔
938

939
    {
17✔
940
        util::CheckedLockGuard lock(m_user_mutex);
17✔
941
        if (!verify_user_present(user)) {
17✔
942
            return completion(AppError(ErrorCodes::ClientUserNotFound, "No user has been found"));
×
943
        }
×
944
    }
17✔
945

946
    if (user->is_logged_in()) {
17✔
947
        log_out(
15✔
948
            user, SyncUser::State::Removed,
15✔
949
            [user, completion = std::move(completion), self = shared_from_this()](const Optional<AppError>& error) {
15✔
950
                user->update_backing_data(std::nullopt);
15✔
951
                if (completion) {
15✔
952
                    completion(error);
14✔
953
                }
14✔
954
            });
15✔
955
    }
15✔
956
    else {
2✔
957
        m_metadata_store->log_out(user->user_id(), SyncUser::State::Removed);
2✔
958
        user->update_backing_data(std::nullopt);
2✔
959
        if (completion) {
2✔
960
            completion(std::nullopt);
2✔
961
        }
2✔
962
    }
2✔
963
}
17✔
964

965
void App::delete_user(const std::shared_ptr<User>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
966
{
12✔
967
    if (!user) {
12✔
968
        return completion(AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found."));
×
969
    }
×
970
    if (user->state() != SyncUser::State::LoggedIn) {
12✔
971
        return completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "User must be logged in to be deleted."));
4✔
972
    }
4✔
973

974
    {
8✔
975
        util::CheckedLockGuard lock(m_user_mutex);
8✔
976
        if (!verify_user_present(user)) {
8✔
977
            return completion(AppError(ErrorCodes::ClientUserNotFound, "No user has been found."));
×
978
        }
×
979
    }
8✔
980

981
    do_authenticated_request(
8✔
982
        HttpMethod::del, url_for_path("/auth/delete"), "", user, RequestTokenType::AccessToken,
8✔
983
        [self = shared_from_this(), completion = std::move(completion), user, this](const Response& response) {
8✔
984
            auto error = AppUtils::check_for_errors(response);
8✔
985
            if (!error) {
8✔
986
                auto user_id = user->user_id();
8✔
987
                user->detach_and_tear_down();
8✔
988
                m_metadata_store->delete_user(*m_file_manager, user_id);
8✔
989
                emit_change_to_subscribers();
8✔
990
            }
8✔
991
            completion(std::move(error));
8✔
992
        });
8✔
993
}
8✔
994

995
void App::link_user(const std::shared_ptr<User>& user, const AppCredentials& credentials,
996
                    UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
997
{
12✔
998
    if (!user) {
12✔
999
        return completion(nullptr,
×
1000
                          AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found."));
×
1001
    }
×
1002
    if (user->state() != SyncUser::State::LoggedIn) {
12✔
1003
        return completion(nullptr,
2✔
1004
                          AppError(ErrorCodes::ClientUserNotLoggedIn, "The specified user is not logged in."));
2✔
1005
    }
2✔
1006
    if (credentials.provider() == AuthProvider::ANONYMOUS) {
10✔
1007
        return completion(nullptr, AppError(ErrorCodes::ClientUserAlreadyNamed,
×
1008
                                            "Cannot add anonymous credentials to an existing user."));
×
1009
    }
×
1010

1011
    log_in_with_credentials(credentials, user, std::move(completion));
10✔
1012
}
10✔
1013

1014
std::shared_ptr<User> App::create_fake_user_for_testing(const std::string& user_id, const std::string& access_token,
1015
                                                        const std::string& refresh_token)
1016
{
×
1017
    std::shared_ptr<User> user;
×
1018
    {
×
1019
        m_metadata_store->create_user(user_id, refresh_token, access_token, "fake_device");
×
1020
        util::CheckedLockGuard lock(m_user_mutex);
×
1021
        user_data_updated(user_id); // FIXME: needs to be callback from metadata store
×
1022
        user = get_user_for_id(user_id);
×
1023
    }
×
1024

1025
    switch_user(user);
×
1026
    return user;
×
1027
}
×
1028

1029

1030
void App::refresh_custom_data(const std::shared_ptr<User>& user,
1031
                              UniqueFunction<void(Optional<AppError>)>&& completion)
1032
{
14✔
1033
    refresh_access_token(user, false, std::move(completion));
14✔
1034
}
14✔
1035

1036
void App::refresh_custom_data(const std::shared_ptr<User>& user, bool update_location,
1037
                              UniqueFunction<void(Optional<AppError>)>&& completion)
1038
{
10✔
1039
    refresh_access_token(user, update_location, std::move(completion));
10✔
1040
}
10✔
1041

1042
std::string App::url_for_path(const std::string& path = "") const
1043
{
5,143✔
1044
    util::CheckedLockGuard guard(m_route_mutex);
5,143✔
1045
    return util::format("%1%2", m_base_route, path);
5,143✔
1046
}
5,143✔
1047

1048
std::string App::get_app_route(const Optional<std::string>& hostname) const
1049
{
4,422✔
1050
    if (hostname) {
4,422✔
1051
        return util::format("%1%2%3/%4", *hostname, s_base_path, s_app_path, m_config.app_id);
16✔
1052
    }
16✔
1053
    return m_app_route;
4,406✔
1054
}
4,422✔
1055

1056
void App::request_location(UniqueFunction<void(std::optional<AppError>)>&& completion,
1057
                           std::optional<std::string>&& new_hostname, std::optional<std::string>&& redir_location,
1058
                           int redirect_count)
1059
{
4,422✔
1060
    // Request the new location information at the new base url hostname; or redir response location if a redirect
1061
    // occurred during the initial location request. redirect_count is used to track the number of sequential
1062
    // redirect responses received during the location update and return an error if this count exceeds
1063
    // max_http_redirects. If neither new_hostname nor redir_location is provided, the current value of m_base_url
1064
    // will be used.
1065
    std::string app_route;
4,422✔
1066
    std::string base_url;
4,422✔
1067
    {
4,422✔
1068
        util::CheckedUniqueLock lock(m_route_mutex);
4,422✔
1069
        // Skip if the location info has already been initialized and a new hostname is not provided
1070
        if (!new_hostname && !redir_location && m_location_updated) {
4,422✔
1071
            // Release the lock before calling the completion function
1072
            lock.unlock();
×
1073
            completion(util::none);
×
1074
            return;
×
1075
        }
×
1076
        base_url = new_hostname.value_or(m_base_url);
4,422✔
1077
        // If this is for a redirect after querying new_hostname, then use the redirect location
1078
        if (redir_location)
4,422✔
1079
            app_route = get_app_route(redir_location);
2✔
1080
        // If this is querying the new_hostname, then use that location
1081
        else if (new_hostname)
4,420✔
1082
            app_route = get_app_route(new_hostname);
14✔
1083
        else
4,406✔
1084
            app_route = get_app_route();
4,406✔
1085
        REALM_ASSERT(!app_route.empty());
4,422✔
1086
    }
4,422✔
1087

1088
    Request req;
×
1089
    req.method = HttpMethod::get;
4,422✔
1090
    req.url = util::format("%1/location", app_route);
4,422✔
1091
    req.timeout_ms = m_request_timeout_ms;
4,422✔
1092

1093
    log_debug("App: request location: %1", req.url);
4,422✔
1094

1095
    m_config.transport->send_request_to_server(req, [self = shared_from_this(), completion = std::move(completion),
4,422✔
1096
                                                     base_url = std::move(base_url),
4,422✔
1097
                                                     redirect_count](const Response& response) mutable {
4,422✔
1098
        // Check to see if a redirect occurred
1099
        if (AppUtils::is_redirect_status_code(response.http_status_code)) {
4,422✔
1100
            // Make sure we don't do too many redirects (max_http_redirects (20) is an arbitrary number)
1101
            if (redirect_count >= s_max_http_redirects) {
2✔
UNCOV
1102
                completion(AppError{ErrorCodes::ClientTooManyRedirects,
×
UNCOV
1103
                                    util::format("number of redirections exceeded %1", s_max_http_redirects),
×
UNCOV
1104
                                    {},
×
UNCOV
1105
                                    response.http_status_code});
×
UNCOV
1106
                return;
×
UNCOV
1107
            }
×
1108
            // Handle the redirect response when requesting the location - extract the
1109
            // new location header field and resend the request.
1110
            auto redir_location = AppUtils::extract_redir_location(response.headers);
2✔
1111
            if (!redir_location) {
2✔
1112
                // Location not found in the response, pass error response up the chain
UNCOV
1113
                completion(AppError{ErrorCodes::ClientRedirectError,
×
UNCOV
1114
                                    "Redirect response missing location header",
×
UNCOV
1115
                                    {},
×
UNCOV
1116
                                    response.http_status_code});
×
UNCOV
1117
                return;
×
UNCOV
1118
            }
×
1119
            // try to request the location info at the new location in the redirect response
1120
            // retry_count is passed in to track the number of subsequent redirection attempts
1121
            self->request_location(std::move(completion), std::move(base_url), std::move(redir_location),
2✔
1122
                                   redirect_count + 1);
2✔
1123
            return;
2✔
1124
        }
2✔
1125

1126
        // Location request was successful - update the location info
1127
        auto update_response = self->update_location(response, base_url);
4,420✔
1128
        if (update_response) {
4,420✔
1129
            self->log_error("App: request location failed (%1%2): %3", update_response->code_string(),
32✔
1130
                            update_response->additional_status_code
32✔
1131
                                ? util::format(" %1", *update_response->additional_status_code)
32✔
1132
                                : "",
32✔
1133
                            update_response->reason());
32✔
1134
        }
32✔
1135
        completion(update_response);
4,420✔
1136
    });
4,420✔
1137
}
4,422✔
1138

1139
std::optional<AppError> App::update_location(const Response& response, const std::string& base_url)
1140
{
4,420✔
1141
    // Validate the location info response for errors and update the stored location info if it is
1142
    // a valid response. base_url is the new hostname or m_base_url value when request_location()
1143
    // was called.
1144

1145
    if (auto error = AppUtils::check_for_errors(response)) {
4,420✔
1146
        return error;
32✔
1147
    }
32✔
1148

1149
    // Update the location info with the data from the response
1150
    try {
4,388✔
1151
        auto json = parse<BsonDocument>(response.body);
4,388✔
1152
        auto hostname = get<std::string>(json, "hostname");
4,388✔
1153
        auto ws_hostname = get<std::string>(json, "ws_hostname");
4,388✔
1154
        std::optional<std::string> sync_route;
4,388✔
1155
        read_field(json, "sync_route", sync_route);
4,388✔
1156

1157
        util::CheckedLockGuard guard(m_route_mutex);
4,388✔
1158
        // Update the local hostname and path information
1159
        update_hostname(hostname, ws_hostname, base_url);
4,388✔
1160
        m_location_updated = true;
4,388✔
1161
        if (!sync_route) {
4,388✔
1162
            sync_route = make_sync_route();
4,380✔
1163
        }
4,380✔
1164
        m_sync_manager->set_sync_route(*sync_route, true);
4,388✔
1165
    }
4,388✔
1166
    catch (const AppError& ex) {
4,388✔
1167
        return ex;
×
1168
    }
×
1169
    return util::none;
4,388✔
1170
}
4,388✔
1171

1172
void App::update_location_and_resend(std::unique_ptr<Request>&& request, IntermediateCompletion&& completion,
1173
                                     Optional<std::string>&& redir_location)
1174
{
4,406✔
1175
    // Update the location information if a redirect response was received or m_location_updated == false
1176
    // and then send the request to the server with request.url updated to the new AppServices hostname.
1177
    request_location(
4,406✔
1178
        [completion = std::move(completion), request = std::move(request),
4,406✔
1179
         self = shared_from_this()](Optional<AppError> error) mutable {
4,406✔
1180
            if (error) {
4,406✔
1181
                // Operation failed, pass it up the chain
1182
                return completion(std::move(request), AppUtils::make_apperror_response(*error));
28✔
1183
            }
28✔
1184

1185
            // If the location info was updated, update the original request to point
1186
            // to the new location URL.
1187
            auto url = util::Uri::parse(request->url);
4,378✔
1188
            request->url =
4,378✔
1189
                util::format("%1%2%3%4", self->get_host_url(), url.get_path(), url.get_query(), url.get_frag());
4,378✔
1190

1191
            self->log_debug("App: send_request(after location update): %1 %2", request->method, request->url);
4,378✔
1192
            // Retry the original request with the updated url
1193
            auto& request_ref = *request;
4,378✔
1194
            self->m_config.transport->send_request_to_server(
4,378✔
1195
                request_ref, [self = std::move(self), completion = std::move(completion),
4,378✔
1196
                              request = std::move(request)](const Response& response) mutable {
4,378✔
1197
                    self->check_for_redirect_response(std::move(request), response, std::move(completion));
4,378✔
1198
                });
4,378✔
1199
        },
4,378✔
1200
        // The base_url is not changing for this request
1201
        util::none, std::move(redir_location));
4,406✔
1202
}
4,406✔
1203

1204
void App::post(std::string&& route, UniqueFunction<void(Optional<AppError>)>&& completion, const BsonDocument& body)
1205
{
4,836✔
1206
    do_request(
4,836✔
1207
        make_request(HttpMethod::post, std::move(route), nullptr, RequestTokenType::NoAuth, Bson(body).to_string()),
4,836✔
1208
        [completion = std::move(completion)](auto&&, const Response& response) {
4,836✔
1209
            completion(AppUtils::check_for_errors(response));
4,836✔
1210
        });
4,836✔
1211
}
4,836✔
1212

1213
void App::do_request(std::unique_ptr<Request>&& request, IntermediateCompletion&& completion, bool update_location)
1214
{
15,495✔
1215
    // Verify the request URL to make sure it is valid
1216
    util::Uri::parse(request->url);
15,495✔
1217

1218
    // Refresh the location info when app is created or when requested (e.g. after a websocket redirect)
1219
    // to ensure the http and websocket URL information is up to date.
1220
    {
15,495✔
1221
        util::CheckedUniqueLock lock(m_route_mutex);
15,495✔
1222
        if (update_location) {
15,495✔
1223
            // If requesting a location update, force the location to be updated before sending the request.
1224
            m_location_updated = false;
47✔
1225
        }
47✔
1226
        if (!m_location_updated) {
15,495✔
1227
            lock.unlock();
4,406✔
1228
            // Location info needs to be requested, update the location info and then send the request
1229
            update_location_and_resend(std::move(request), std::move(completion));
4,406✔
1230
            return;
4,406✔
1231
        }
4,406✔
1232
    }
15,495✔
1233

1234
    log_debug("App: do_request: %1 %2", request->method, request->url);
11,089✔
1235
    // If location info has already been updated, then send the request directly
1236
    auto& request_ref = *request;
11,089✔
1237
    m_config.transport->send_request_to_server(
11,089✔
1238
        request_ref, [self = shared_from_this(), completion = std::move(completion),
11,089✔
1239
                      request = std::move(request)](const Response& response) mutable {
11,089✔
1240
            self->check_for_redirect_response(std::move(request), response, std::move(completion));
11,089✔
1241
        });
11,089✔
1242
}
11,089✔
1243

1244
void App::check_for_redirect_response(std::unique_ptr<Request>&& request, const Response& response,
1245
                                      IntermediateCompletion&& completion)
1246
{
15,467✔
1247
    // If this isn't a redirect response, then we're done
1248
    if (!AppUtils::is_redirect_status_code(response.http_status_code)) {
15,467✔
1249
        return completion(std::move(request), response);
15,467✔
1250
    }
15,467✔
1251

1252
    // Handle a redirect response when sending the original request - extract the location
1253
    // header field and resend the request.
1254
    auto redir_location = AppUtils::extract_redir_location(response.headers);
×
1255
    if (!redir_location) {
×
1256
        // Location not found in the response, pass error response up the chain
1257
        return completion(std::move(request),
×
1258
                          AppUtils::make_clienterror_response(ErrorCodes::ClientRedirectError,
×
1259
                                                              "Redirect response missing location header",
×
1260
                                                              response.http_status_code));
×
1261
    }
×
1262

1263
    // Request the location info at the new location - once this is complete, the original
1264
    // request will be sent to the new server
1265
    update_location_and_resend(std::move(request), std::move(completion), std::move(redir_location));
×
1266
}
×
1267

1268
void App::do_authenticated_request(HttpMethod method, std::string&& route, std::string&& body,
1269
                                   const std::shared_ptr<User>& user, RequestTokenType token_type,
1270
                                   util::UniqueFunction<void(const Response&)>&& completion)
1271
{
5,590✔
1272
    auto request = make_request(method, std::move(route), user, token_type, std::move(body));
5,590✔
1273
    do_request(std::move(request), [token_type, user, completion = std::move(completion), self = shared_from_this()](
5,590✔
1274
                                       std::unique_ptr<Request>&& request, const Response& response) mutable {
5,590✔
1275
        if (auto error = AppUtils::check_for_errors(response)) {
5,590✔
1276
            self->handle_auth_failure(std::move(*error), std::move(request), response, user, token_type,
66✔
1277
                                      std::move(completion));
66✔
1278
        }
66✔
1279
        else {
5,524✔
1280
            completion(response);
5,524✔
1281
        }
5,524✔
1282
    });
5,590✔
1283
}
5,590✔
1284

1285
void App::handle_auth_failure(const AppError& error, std::unique_ptr<Request>&& request, const Response& response,
1286
                              const std::shared_ptr<User>& user, RequestTokenType token_type,
1287
                              util::UniqueFunction<void(const Response&)>&& completion)
1288
{
66✔
1289
    // Only handle auth failures
1290
    if (*error.additional_status_code != 401) {
66✔
1291
        completion(response);
40✔
1292
        return;
40✔
1293
    }
40✔
1294

1295
    // If the refresh token is invalid then the user needs to be logged back
1296
    // in to be able to use it again
1297
    if (token_type == RequestTokenType::RefreshToken) {
26✔
1298
        if (user && user->is_logged_in()) {
18!
1299
            user->log_out();
×
1300
        }
×
1301
        completion(response);
18✔
1302
        return;
18✔
1303
    }
18✔
1304

1305
    // Otherwise we may be able to request a new access token and have the request succeed with that
1306
    refresh_access_token(user, false,
8✔
1307
                         [self = shared_from_this(), request = std::move(request), completion = std::move(completion),
8✔
1308
                          response = std::move(response), user](Optional<AppError>&& error) mutable {
8✔
1309
                             if (error) {
8✔
1310
                                 // pass the error back up the chain
1311
                                 completion(response);
4✔
1312
                                 return;
4✔
1313
                             }
4✔
1314

1315
                             // Reissue the request with the new access token
1316
                             request->headers = get_request_headers(user, RequestTokenType::AccessToken);
4✔
1317
                             self->do_request(std::move(request),
4✔
1318
                                              [completion = std::move(completion)](auto&&, auto& response) {
4✔
1319
                                                  completion(response);
4✔
1320
                                              });
4✔
1321
                         });
4✔
1322
}
8✔
1323

1324
/// MARK: - refresh access token
1325
void App::refresh_access_token(const std::shared_ptr<User>& user, bool update_location,
1326
                               util::UniqueFunction<void(Optional<AppError>)>&& completion)
1327
{
115✔
1328
    if (!user) {
115✔
1329
        completion(AppError(ErrorCodes::ClientUserNotFound, "No current user exists"));
2✔
1330
        return;
2✔
1331
    }
2✔
1332

1333
    if (!user->is_logged_in()) {
113✔
1334
        completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "The user is not logged in"));
2✔
1335
        return;
2✔
1336
    }
2✔
1337

1338
    log_debug("App: refresh_access_token: user_id: %1%2", user->user_id(),
111✔
1339
              update_location ? " (updating location)" : "");
111✔
1340

1341
    // If update_location is set, force the location info to be updated before sending the request
1342
    do_request(
111✔
1343
        make_request(HttpMethod::post, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, ""),
111✔
1344
        [completion = std::move(completion), self = shared_from_this(), user](auto&&, const Response& response) {
111✔
1345
            if (auto error = AppUtils::check_for_errors(response)) {
111✔
1346
                self->log_error("App: refresh_access_token: %1 -> %2 ERROR: %3", user->user_id(),
64✔
1347
                                response.http_status_code, error->what());
64✔
1348

1349
                return completion(std::move(error));
64✔
1350
            }
64✔
1351

1352
            try {
47✔
1353
                auto json = parse<BsonDocument>(response.body);
47✔
1354
                RealmJWT access_token{get<std::string>(json, "access_token")};
47✔
1355
                self->m_metadata_store->update_user(user->user_id(), [&](auto& data) {
47✔
1356
                    data.access_token = access_token;
45✔
1357
                    user->update_backing_data(data);
45✔
1358
                });
45✔
1359
            }
47✔
1360
            catch (AppError& err) {
47✔
1361
                return completion(std::move(err));
2✔
1362
            }
2✔
1363

1364
            return completion(util::none);
45✔
1365
        },
47✔
1366
        update_location);
111✔
1367
}
111✔
1368

1369
std::string App::function_call_url_path() const
1370
{
612✔
1371
    util::CheckedLockGuard guard(m_route_mutex);
612✔
1372
    return util::format("%1/functions/call", m_app_route);
612✔
1373
}
612✔
1374

1375
void App::call_function(const std::shared_ptr<User>& user, const std::string& name, std::string_view args_ejson,
1376
                        const Optional<std::string>& service_name_opt,
1377
                        UniqueFunction<void(const std::string*, Optional<AppError>)>&& completion)
1378
{
604✔
1379
    auto service_name = service_name_opt ? *service_name_opt : "<none>";
604✔
1380
    if (would_log(util::Logger::Level::debug)) {
604✔
1381
        log_debug("App: call_function: %1 service_name: %2 args_bson: %3", name, service_name, args_ejson);
604✔
1382
    }
604✔
1383

1384
    auto args = util::format("{\"arguments\":%1,\"name\":%2%3}", args_ejson, nlohmann::json(name).dump(),
604✔
1385
                             service_name_opt ? (",\"service\":" + nlohmann::json(service_name).dump()) : "");
604✔
1386

1387
    do_authenticated_request(HttpMethod::post, function_call_url_path(), std::move(args), user,
604✔
1388
                             RequestTokenType::AccessToken,
604✔
1389
                             [self = shared_from_this(), name = name, service_name = std::move(service_name),
604✔
1390
                              completion = std::move(completion)](const Response& response) {
604✔
1391
                                 if (auto error = AppUtils::check_for_errors(response)) {
604✔
1392
                                     self->log_error("App: call_function: %1 service_name: %2 -> %3 ERROR: %4", name,
18✔
1393
                                                     service_name, response.http_status_code, error->what());
18✔
1394
                                     return completion(nullptr, error);
18✔
1395
                                 }
18✔
1396
                                 completion(&response.body, util::none);
586✔
1397
                             });
586✔
1398
}
604✔
1399

1400
void App::call_function(const std::shared_ptr<User>& user, const std::string& name, const BsonArray& args_bson,
1401
                        const Optional<std::string>& service_name,
1402
                        UniqueFunction<void(Optional<Bson>&&, Optional<AppError>)>&& completion)
1403
{
604✔
1404
    auto service_name2 = service_name ? *service_name : "<none>";
604✔
1405
    std::stringstream args_ejson;
604✔
1406
    args_ejson << "[";
604✔
1407
    bool not_first = false;
604✔
1408
    for (auto&& arg : args_bson) {
620✔
1409
        if (not_first)
620✔
1410
            args_ejson << ',';
16✔
1411
        args_ejson << arg.toJson();
620✔
1412
        not_first = true;
620✔
1413
    }
620✔
1414
    args_ejson << "]";
604✔
1415

1416
    call_function(user, name, std::move(args_ejson).str(), service_name,
604✔
1417
                  [self = shared_from_this(), name, service_name = std::move(service_name2),
604✔
1418
                   completion = std::move(completion)](const std::string* response, util::Optional<AppError> err) {
604✔
1419
                      if (err) {
604✔
1420
                          return completion({}, err);
18✔
1421
                      }
18✔
1422
                      if (!response) {
586✔
1423
                          return completion({}, AppError{ErrorCodes::AppUnknownError, "Empty response from server"});
×
1424
                      }
×
1425
                      util::Optional<Bson> body_as_bson;
586✔
1426
                      try {
586✔
1427
                          body_as_bson = bson::parse(*response);
586✔
1428
                          if (self->would_log(util::Logger::Level::debug)) {
586✔
1429
                              self->log_debug("App: call_function: %1 service_name: %2 - results: %3", name,
586✔
1430
                                              service_name, body_as_bson ? body_as_bson->to_string() : "<none>");
586✔
1431
                          }
586✔
1432
                      }
586✔
1433
                      catch (const std::exception& e) {
586✔
1434
                          self->log_error("App: call_function: %1 service_name: %2 - error parsing result: %3", name,
×
1435
                                          service_name, e.what());
×
1436
                          return completion(util::none, AppError(ErrorCodes::BadBsonParse, e.what()));
×
1437
                      };
586✔
1438
                      completion(std::move(body_as_bson), util::none);
586✔
1439
                  });
586✔
1440
}
604✔
1441

1442
void App::call_function(const std::shared_ptr<User>& user, const std::string& name, const BsonArray& args_bson,
1443
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1444
{
4✔
1445
    call_function(user, name, args_bson, util::none, std::move(completion));
4✔
1446
}
4✔
1447

1448
void App::call_function(const std::string& name, const BsonArray& args_bson,
1449
                        const Optional<std::string>& service_name,
1450
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1451
{
×
1452
    call_function(current_user(), name, args_bson, service_name, std::move(completion));
×
1453
}
×
1454

1455
void App::call_function(const std::string& name, const BsonArray& args_bson,
1456
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1457
{
2✔
1458
    call_function(current_user(), name, args_bson, std::move(completion));
2✔
1459
}
2✔
1460

1461
Request App::make_streaming_request(const std::shared_ptr<User>& user, const std::string& name,
1462
                                    const BsonArray& args_bson, const Optional<std::string>& service_name) const
1463
{
8✔
1464
    auto args = BsonDocument{
8✔
1465
        {"arguments", args_bson},
8✔
1466
        {"name", name},
8✔
1467
    };
8✔
1468
    if (service_name) {
8✔
1469
        args["service"] = *service_name;
8✔
1470
    }
8✔
1471
    const auto args_json = Bson(args).to_string();
8✔
1472

1473
    auto args_base64 = std::string(util::base64_encoded_size(args_json.size()), '\0');
8✔
1474
    util::base64_encode(args_json, args_base64);
8✔
1475

1476
    auto url = function_call_url_path() + "?baas_request=" + util::uri_percent_encode(args_base64);
8✔
1477
    if (user) {
8✔
1478
        url += "&baas_at=";
2✔
1479
        url += user->access_token(); // doesn't need url encoding
2✔
1480
    }
2✔
1481

1482
    return Request{
8✔
1483
        HttpMethod::get,
8✔
1484
        url,
8✔
1485
        m_request_timeout_ms,
8✔
1486
        {{"Accept", "text/event-stream"}},
8✔
1487
    };
8✔
1488
}
8✔
1489

1490
std::unique_ptr<Request> App::make_request(HttpMethod method, std::string&& url, const std::shared_ptr<User>& user,
1491
                                           RequestTokenType token_type, std::string&& body) const
1492
{
15,491✔
1493
    auto request = std::make_unique<Request>();
15,491✔
1494
    request->method = method;
15,491✔
1495
    request->url = std::move(url);
15,491✔
1496
    request->body = std::move(body);
15,491✔
1497
    request->headers = get_request_headers(user, token_type);
15,491✔
1498
    request->timeout_ms = m_request_timeout_ms;
15,491✔
1499
    return request;
15,491✔
1500
}
15,491✔
1501

1502
PushClient App::push_notification_client(const std::string& service_name)
1503
{
10✔
1504
    return PushClient(service_name, m_config.app_id, std::shared_ptr<AuthRequestClient>(shared_from_this(), this));
10✔
1505
}
10✔
1506

1507
void App::emit_change_to_subscribers()
1508
{
4,954✔
1509
    // This wrapper is needed only to be able to add the `REQUIRES(!m_user_mutex)`
1510
    // annotation. Calling this function with the lock held leads to a deadlock
1511
    // if any of the listeners try to access us.
1512
    Subscribable<App>::emit_change_to_subscribers(*this);
4,954✔
1513
}
4,954✔
1514

1515
// MARK: - UserProvider
1516

1517
void App::register_sync_user(User& user)
1518
{
9,595✔
1519
    auto& tracked_user = m_users[user.user_id()];
9,595✔
1520
    REALM_ASSERT(!tracked_user || !tracked_user->weak_from_this().lock());
9,595!
1521
    tracked_user = &user;
9,595✔
1522
    user.update_backing_data(m_metadata_store->get_user(user.user_id()));
9,595✔
1523
}
9,595✔
1524

1525
void App::unregister_sync_user(User& user)
1526
{
9,595✔
1527
    util::CheckedLockGuard lock(m_user_mutex);
9,595✔
1528
    auto it = m_users.find(user.user_id());
9,595✔
1529
    REALM_ASSERT(it != m_users.end());
9,595✔
1530
    // If the user was requested while we were waiting for the lock, it may
1531
    // have already been replaced with a new instance for the same user id
1532
    if (it != m_users.end() && it->second == &user) {
9,595✔
1533
        m_users.erase(it);
9,595✔
1534
    }
9,595✔
1535
    if (m_current_user == &user) {
9,595✔
1536
        m_current_user = nullptr;
9,371✔
1537
    }
9,371✔
1538
}
9,595✔
1539

1540
bool App::immediately_run_file_actions(std::string_view realm_path)
1541
{
10✔
1542
    return m_metadata_store->immediately_run_file_actions(*m_file_manager, realm_path);
10✔
1543
}
10✔
1544

1545
std::string App::path_for_realm(const SyncConfig& config, std::optional<std::string> custom_file_name) const
1546
{
28✔
1547
    return m_file_manager->path_for_realm(config, std::move(custom_file_name));
28✔
1548
}
28✔
1549

1550
} // namespace realm::app
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