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

realm / realm-core / 2213

10 Apr 2024 11:21PM UTC coverage: 91.792% (-0.8%) from 92.623%
2213

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94842 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1861 existing lines in 82 files now uncovered.

242866 of 264583 relevant lines covered (91.79%)

5593111.45 hits per line

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

90.72
/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
{
65,041✔
60
    if (holds_alternative<T>(bson)) {
65,041✔
61
        return static_cast<T>(bson);
65,041✔
62
    }
65,041✔
UNCOV
63
    throw_json_error(ErrorCodes::MalformedJson, "?");
×
UNCOV
64
}
×
65

66
template <typename T>
67
T get(const BsonDocument& doc, const std::string& key)
68
{
46,418✔
69
    if (auto val = doc.find(key)) {
46,418✔
70
        return as<T>(*val);
46,418✔
71
    }
46,418✔
72
    throw_json_error(ErrorCodes::MissingJsonKey, key);
×
UNCOV
73
    return {};
×
UNCOV
74
}
×
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 {
×
UNCOV
83
        throw_json_error(ErrorCodes::MissingJsonKey, key);
×
UNCOV
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,267✔
96
    if (auto val = data.find(key)) {
4,267✔
97
        value = as<T>(*val);
16✔
98
    }
16✔
99
}
4,267✔
100

101
template <typename T>
102
T parse(std::string_view str)
103
{
13,773✔
104
    try {
13,773✔
105
        return as<T>(bson::parse(str));
13,773✔
106
    }
13,773✔
107
    catch (const std::exception& e) {
2✔
108
        throw_json_error(ErrorCodes::MalformedJson, e.what());
2✔
109
    }
2✔
110
}
13,773✔
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

11✔
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) {
11✔
UNCOV
125
            completion({}, std::move(e));
×
UNCOV
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
{
14,950✔
144
    HttpHeaders headers{{"Content-Type", "application/json;charset=utf-8"}, {"Accept", "application/json"}};
14,950✔
145
    if (user) {
14,950✔
146
        switch (token_type) {
5,504✔
UNCOV
147
            case RequestTokenType::NoAuth:
✔
UNCOV
148
                break;
×
149
            case RequestTokenType::AccessToken:
5,302✔
150
                headers.insert({"Authorization", util::format("Bearer %1", user->access_token())});
5,302✔
151
                break;
5,302✔
152
            case RequestTokenType::RefreshToken:
202✔
153
                headers.insert({"Authorization", util::format("Bearer %1", user->refresh_token())});
202✔
154
                break;
202✔
155
        }
14,950✔
156
    }
14,950✔
157
    return headers;
14,950✔
158
}
14,950✔
159

160
UniqueFunction<void(const Response&)> handle_default_response(UniqueFunction<void(Optional<AppError>)>&& completion)
161
{
24✔
162
    return [completion = std::move(completion)](const Response& response) {
24✔
163
        completion(AppUtils::check_for_errors(response));
24✔
164
    };
24✔
165
}
24✔
166

167
constexpr static std::string_view s_base_path = "/api/client/v2.0";
168
constexpr static std::string_view s_app_path = "/app";
169
constexpr static std::string_view s_auth_path = "/auth";
170
constexpr static std::string_view s_sync_path = "/realm-sync";
171
constexpr static uint64_t s_default_timeout_ms = 60000;
172
constexpr static std::string_view s_username_password_provider_key = "local-userpass";
173
constexpr static std::string_view s_user_api_key_provider_key_path = "api_keys";
174
constexpr static int s_max_http_redirects = 20;
175
static util::FlatMap<std::string, util::FlatMap<std::string, SharedApp>> s_apps_cache; // app_id -> base_url -> app
176
std::mutex s_apps_mutex;
177
} // anonymous namespace
178

179
namespace realm::app {
180

181
std::string_view App::default_base_url()
182
{
4,287✔
183
    return "https://services.cloud.mongodb.com";
4,287✔
184
}
4,287✔
185

186
// NO_THREAD_SAFETY_ANALYSIS because clang generates a false positive.
187
// "Calling function configure requires negative capability '!app->m_route_mutex'"
188
// But 'app' is an object just created in this static method so it is not possible to annotate this in the header.
189
SharedApp App::get_app(CacheMode mode, const AppConfig& config) NO_THREAD_SAFETY_ANALYSIS
190
{
4,241✔
191
    if (mode == CacheMode::Enabled) {
4,241✔
192
        std::lock_guard lock(s_apps_mutex);
10✔
193
        auto& app = s_apps_cache[config.app_id][config.base_url.value_or(std::string(App::default_base_url()))];
10✔
194
        if (!app) {
10✔
195
            app = std::make_shared<App>(Private(), config);
6✔
196
        }
6✔
197
        return app;
10✔
198
    }
10✔
199
    REALM_ASSERT(mode == CacheMode::Disabled);
4,231✔
200
    return std::make_shared<App>(Private(), config);
4,231✔
201
}
4,231✔
202

203
SharedApp App::get_cached_app(const std::string& app_id, const std::optional<std::string>& base_url)
204
{
10✔
205
    std::lock_guard lock(s_apps_mutex);
10✔
206
    if (auto it = s_apps_cache.find(app_id); it != s_apps_cache.end()) {
10✔
207
        const auto& apps_by_url = it->second;
10✔
208

5✔
209
        auto app_it = base_url ? apps_by_url.find(*base_url) : apps_by_url.begin();
9✔
210
        if (app_it != apps_by_url.end()) {
10✔
211
            return app_it->second;
8✔
212
        }
8✔
213
    }
2✔
214

1✔
215
    return nullptr;
2✔
216
}
2✔
217

218
void App::clear_cached_apps()
219
{
4,425✔
220
    std::lock_guard lock(s_apps_mutex);
4,425✔
221
    s_apps_cache.clear();
4,425✔
222
}
4,425✔
223

224
void App::close_all_sync_sessions()
UNCOV
225
{
×
UNCOV
226
    std::lock_guard lock(s_apps_mutex);
×
UNCOV
227
    for (auto& apps_by_url : s_apps_cache) {
×
UNCOV
228
        for (auto& app : apps_by_url.second) {
×
UNCOV
229
            app.second->sync_manager()->close_all_sessions();
×
UNCOV
230
        }
×
UNCOV
231
    }
×
UNCOV
232
}
×
233

234
App::App(Private, const AppConfig& config)
235
    : m_config(config)
236
    , m_base_url(m_config.base_url.value_or(std::string(App::default_base_url())))
237
    , m_request_timeout_ms(m_config.default_request_timeout_ms.value_or(s_default_timeout_ms))
238
    , m_file_manager(std::make_unique<SyncFileManager>(config))
239
    , m_metadata_store(create_metadata_store(config, *m_file_manager))
240
    , m_sync_manager(SyncManager::create(config.sync_client_config))
241
{
4,237✔
242
#ifdef __EMSCRIPTEN__
243
    if (!m_config.transport) {
244
        m_config.transport = std::make_shared<_impl::EmscriptenNetworkTransport>();
245
    }
246
#endif
247
    REALM_ASSERT(m_config.transport);
4,237✔
248

2,110✔
249
    // if a base url is provided, then verify the value
2,110✔
250
    if (m_config.base_url) {
4,237✔
251
        util::Uri::parse(*m_config.base_url);
567✔
252
    }
567✔
253
    // Setup a baseline set of routes using the provided or default base url
2,110✔
254
    // These will be updated when the location info is refreshed prior to sending the
2,110✔
255
    // first AppServices HTTP request.
2,110✔
256
    configure_route(m_base_url, "");
4,237✔
257
    m_sync_manager->set_sync_route(make_sync_route(), false);
4,237✔
258

2,110✔
259
    if (m_config.device_info.platform_version.empty()) {
4,237✔
260
        throw InvalidArgument("You must specify the Platform Version in App::Config::device_info");
×
261
    }
×
262

2,110✔
263
    if (m_config.device_info.sdk.empty()) {
4,237✔
264
        throw InvalidArgument("You must specify the SDK Name in App::Config::device_info");
×
UNCOV
265
    }
×
266

2,110✔
267
    if (m_config.device_info.sdk_version.empty()) {
4,237✔
UNCOV
268
        throw InvalidArgument("You must specify the SDK Version in App::Config::device_info");
×
UNCOV
269
    }
×
270
}
4,237✔
271

272
App::~App() {}
4,237✔
273

274
bool App::init_logger()
275
{
44,788✔
276
    if (!m_logger_ptr) {
44,788✔
277
        m_logger_ptr = m_sync_manager->get_logger();
4,229✔
278
        if (!m_logger_ptr) {
4,229✔
UNCOV
279
            m_logger_ptr = util::Logger::get_default_logger();
×
UNCOV
280
        }
×
281
    }
4,229✔
282
    return bool(m_logger_ptr);
44,788✔
283
}
44,788✔
284

285
bool App::would_log(util::Logger::Level level)
286
{
5,830✔
287
    return init_logger() && m_logger_ptr->would_log(util::LogCategory::app, level);
5,830✔
288
}
5,830✔
289

290
template <class... Params>
291
void App::log_debug(const char* message, Params&&... params)
292
{
38,898✔
293
    if (init_logger()) {
38,898✔
294
        m_logger_ptr->log(util::LogCategory::app, util::Logger::Level::debug, message,
38,898✔
295
                          std::forward<Params>(params)...);
38,898✔
296
    }
38,898✔
297
}
38,898✔
298

299
template <class... Params>
300
void App::log_error(const char* message, Params&&... params)
301
{
60✔
302
    if (init_logger()) {
60!
303
        m_logger_ptr->log(util::LogCategory::app, util::Logger::Level::error, message,
60✔
304
                          std::forward<Params>(params)...);
60✔
305
    }
60✔
306
}
60✔
307

308
std::string App::auth_route()
309
{
9,436✔
310
    util::CheckedLockGuard guard(m_route_mutex);
9,436✔
311
    return m_auth_route;
9,436✔
312
}
9,436✔
313

314
std::string App::base_url()
UNCOV
315
{
×
UNCOV
316
    util::CheckedLockGuard guard(m_route_mutex);
×
UNCOV
317
    return m_base_url;
×
UNCOV
318
}
×
319

320
std::string App::get_host_url()
321
{
4,301✔
322
    util::CheckedLockGuard guard(m_route_mutex);
4,301✔
323
    return m_host_url;
4,301✔
324
}
4,301✔
325

326
std::string App::get_ws_host_url()
327
{
76✔
328
    util::CheckedLockGuard guard(m_route_mutex);
76✔
329
    return m_ws_host_url;
76✔
330
}
76✔
331

332
std::string App::make_sync_route(Optional<std::string> ws_host_url)
333
{
8,464✔
334
    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,464✔
335
                        s_sync_path);
8,464✔
336
}
8,464✔
337

338
void App::configure_route(const std::string& host_url, const std::string& ws_host_url)
339
{
8,472✔
340
    m_host_url = host_url;
8,472✔
341
    m_ws_host_url = ws_host_url;
8,472✔
342
    if (m_ws_host_url.empty())
8,472✔
343
        m_ws_host_url = App::create_ws_host_url(m_host_url);
4,237✔
344

4,219✔
345
    // host_url is the url to the server: e.g., https://services.cloud.mongodb.com or https://localhost:9090
4,219✔
346
    // base_route is the baseline client api path: e.g. <host_url>/api/client/v2.0
4,219✔
347
    m_base_route = util::format("%1%2", m_host_url, s_base_path);
8,472✔
348
    // app_route is the cloud app URL: <host_url>/api/client/v2.0/app/<app_id>
4,219✔
349
    m_app_route = util::format("%1%2/%3", m_base_route, s_app_path, m_config.app_id);
8,472✔
350
    // auth_route is cloud app auth URL: <host_url>/api/client/v2.0/app/<app_id>/auth
4,219✔
351
    m_auth_route = util::format("%1%2", m_app_route, s_auth_path);
8,472✔
352
}
8,472✔
353

354
// Create a temporary websocket URL domain using the given host URL
355
// This updates the URL based on the following assumptions:
356
// If the URL doesn't start with 'http' => <host-url>
357
// http[s]://[region-prefix]realm.mongodb.com => ws[s]://ws.[region-prefix]realm.mongodb.com
358
// http[s]://[region-prefix]services.cloud.mongodb.com => ws[s]://[region-prefix].ws.services.cloud.mongodb.com
359
// All others => http[s]://<host-url> => ws[s]://<host-url>
360
std::string App::create_ws_host_url(std::string_view host_url)
361
{
4,383✔
362
    constexpr static std::string_view old_base_domain = "realm.mongodb.com";
4,383✔
363
    constexpr static std::string_view new_base_domain = "services.cloud.mongodb.com";
4,383✔
364
    const size_t base_len = std::char_traits<char>::length("http://");
4,383✔
365

2,183✔
366
    // Doesn't contain 7 or more characters (length of 'http://') or start with http,
2,183✔
367
    // just return provided string
2,183✔
368
    if (host_url.length() < base_len || host_url.substr(0, 4) != "http") {
4,383✔
369
        return std::string(host_url);
2✔
370
    }
2✔
371
    // If it starts with 'https' then ws url will start with 'wss'
2,182✔
372
    bool https = host_url[4] == 's';
4,381✔
373
    size_t prefix_len = base_len + (https ? 1 : 0);
4,072✔
374
    std::string_view prefix = https ? "wss://" : "ws://";
4,072✔
375

2,182✔
376
    // http[s]://[<region-prefix>]realm.mongodb.com[/<path>] =>
2,182✔
377
    //     ws[s]://ws.[<region-prefix>]realm.mongodb.com[/<path>]
2,182✔
378
    if (host_url.find(old_base_domain) != std::string_view::npos) {
4,381✔
379
        return util::format("%1ws.%2", prefix, host_url.substr(prefix_len));
12✔
380
    }
12✔
381
    // http[s]://[<region-prefix>]services.cloud.mongodb.com[/<path>] =>
2,176✔
382
    //     ws[s]://[<region-prefix>].ws.services.cloud.mongodb.com[/<path>]
2,176✔
383
    if (auto start = host_url.find(new_base_domain); start != std::string_view::npos) {
4,369✔
384
        return util::format("%1%2ws.%3", prefix, host_url.substr(prefix_len, start - prefix_len),
3,696✔
385
                            host_url.substr(start));
3,696✔
386
    }
3,696✔
387

328✔
388
    // All others => http[s]://<host-url>[/<path>] => ws[s]://<host-url>[/<path>]
328✔
389
    return util::format("ws%1", host_url.substr(4));
673✔
390
}
673✔
391

392
void App::update_hostname(const std::string& host_url, const std::string& ws_host_url,
393
                          const std::string& new_base_url)
394
{
4,235✔
395
    log_debug("App: update_hostname: %1 | %2 | %3", host_url, ws_host_url, new_base_url);
4,235✔
396
    m_base_url = new_base_url;
4,235✔
397
    // If a new host url was returned from the server, use it to configure the routes
2,109✔
398
    // Otherwise, use the m_base_url value
2,109✔
399
    std::string base_url = host_url.length() > 0 ? host_url : m_base_url;
4,235✔
400
    configure_route(base_url, ws_host_url);
4,235✔
401
}
4,235✔
402

403
// MARK: - Template specializations
404

405
template <>
406
App::UsernamePasswordProviderClient App::provider_client<App::UsernamePasswordProviderClient>()
407
{
4,664✔
408
    return App::UsernamePasswordProviderClient(shared_from_this());
4,664✔
409
}
4,664✔
410

411
template <>
412
App::UserAPIKeyProviderClient App::provider_client<App::UserAPIKeyProviderClient>()
413
{
20✔
414
    return App::UserAPIKeyProviderClient(*this);
20✔
415
}
20✔
416

417
// MARK: - UsernamePasswordProviderClient
418

419
void App::UsernamePasswordProviderClient::register_email(const std::string& email, const std::string& password,
420
                                                         UniqueFunction<void(Optional<AppError>)>&& completion)
421
{
4,668✔
422
    m_parent->log_debug("App: register_email: %1", email);
4,668✔
423
    m_parent->post(util::format("%1/providers/%2/register", m_parent->auth_route(), s_username_password_provider_key),
4,668✔
424
                   std::move(completion), {{"email", email}, {"password", password}});
4,668✔
425
}
4,668✔
426

427
void App::UsernamePasswordProviderClient::confirm_user(const std::string& token, const std::string& token_id,
428
                                                       UniqueFunction<void(Optional<AppError>)>&& completion)
429
{
2✔
430
    m_parent->log_debug("App: confirm_user");
2✔
431
    m_parent->post(util::format("%1/providers/%2/confirm", m_parent->auth_route(), s_username_password_provider_key),
2✔
432
                   std::move(completion), {{"token", token}, {"tokenId", token_id}});
2✔
433
}
2✔
434

435
void App::UsernamePasswordProviderClient::resend_confirmation_email(
436
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
437
{
2✔
438
    m_parent->log_debug("App: resend_confirmation_email: %1", email);
2✔
439
    m_parent->post(
2✔
440
        util::format("%1/providers/%2/confirm/send", m_parent->auth_route(), s_username_password_provider_key),
2✔
441
        std::move(completion), {{"email", email}});
2✔
442
}
2✔
443

444
void App::UsernamePasswordProviderClient::retry_custom_confirmation(
445
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
446
{
4✔
447
    m_parent->log_debug("App: retry_custom_confirmation: %1", email);
4✔
448
    m_parent->post(
4✔
449
        util::format("%1/providers/%2/confirm/call", m_parent->auth_route(), s_username_password_provider_key),
4✔
450
        std::move(completion), {{"email", email}});
4✔
451
}
4✔
452

453
void App::UsernamePasswordProviderClient::send_reset_password_email(
454
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
UNCOV
455
{
×
UNCOV
456
    m_parent->log_debug("App: send_reset_password_email: %1", email);
×
UNCOV
457
    m_parent->post(
×
UNCOV
458
        util::format("%1/providers/%2/reset/send", m_parent->auth_route(), s_username_password_provider_key),
×
UNCOV
459
        std::move(completion), {{"email", email}});
×
UNCOV
460
}
×
461

462
void App::UsernamePasswordProviderClient::reset_password(const std::string& password, const std::string& token,
463
                                                         const std::string& token_id,
464
                                                         UniqueFunction<void(Optional<AppError>)>&& completion)
465
{
2✔
466
    m_parent->log_debug("App: reset_password");
2✔
467
    m_parent->post(util::format("%1/providers/%2/reset", m_parent->auth_route(), s_username_password_provider_key),
2✔
468
                   std::move(completion), {{"password", password}, {"token", token}, {"tokenId", token_id}});
2✔
469
}
2✔
470

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

481
// MARK: - UserAPIKeyProviderClient
482

483
std::string App::UserAPIKeyProviderClient::url_for_path(const std::string& path = "") const
484
{
82✔
485
    if (!path.empty()) {
82✔
486
        return m_auth_request_client.url_for_path(
58✔
487
            util::format("%1/%2/%3", s_auth_path, s_user_api_key_provider_key_path, path));
58✔
488
    }
58✔
489

12✔
490
    return m_auth_request_client.url_for_path(util::format("%1/%2", s_auth_path, s_user_api_key_provider_key_path));
24✔
491
}
24✔
492

493
void App::UserAPIKeyProviderClient::create_api_key(
494
    const std::string& name, const std::shared_ptr<User>& user,
495
    UniqueFunction<void(UserAPIKey&&, Optional<AppError>)>&& completion)
496
{
10✔
497
    m_auth_request_client.do_authenticated_request(
10✔
498
        HttpMethod::post, url_for_path(), Bson(BsonDocument{{"name", name}}).to_string(), user,
10✔
499
        RequestTokenType::RefreshToken, UserAPIKeyResponseHandler{std::move(completion)});
10✔
500
}
10✔
501

502
void App::UserAPIKeyProviderClient::fetch_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
503
                                                  UniqueFunction<void(UserAPIKey&&, Optional<AppError>)>&& completion)
504
{
34✔
505
    m_auth_request_client.do_authenticated_request(HttpMethod::get, url_for_path(id.to_string()), "", user,
34✔
506
                                                   RequestTokenType::RefreshToken,
34✔
507
                                                   UserAPIKeyResponseHandler{std::move(completion)});
34✔
508
}
34✔
509

510
void App::UserAPIKeyProviderClient::fetch_api_keys(
511
    const std::shared_ptr<User>& user,
512
    UniqueFunction<void(std::vector<UserAPIKey>&&, Optional<AppError>)>&& completion)
513
{
14✔
514
    m_auth_request_client.do_authenticated_request(
14✔
515
        HttpMethod::get, url_for_path(), "", user, RequestTokenType::RefreshToken,
14✔
516
        [completion = std::move(completion)](const Response& response) {
14✔
517
            if (auto error = AppUtils::check_for_errors(response)) {
14✔
518
                return completion({}, std::move(error));
4✔
519
            }
4✔
520

5✔
521
            try {
10✔
522
                auto json = parse<BsonArray>(response.body);
10✔
523
                std::vector<UserAPIKey> keys;
10✔
524
                keys.reserve(json.size());
10✔
525
                for (auto&& api_key_json : json) {
10✔
526
                    keys.push_back(UserAPIKeyResponseHandler::read_user_api_key(as<BsonDocument>(api_key_json)));
10✔
527
                }
10✔
528
                return completion(std::move(keys), {});
10✔
529
            }
10✔
UNCOV
530
            catch (AppError& e) {
×
UNCOV
531
                completion({}, std::move(e));
×
UNCOV
532
            }
×
533
        });
10✔
534
}
14✔
535

536
void App::UserAPIKeyProviderClient::delete_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
537
                                                   UniqueFunction<void(Optional<AppError>)>&& completion)
538
{
8✔
539
    m_auth_request_client.do_authenticated_request(HttpMethod::del, url_for_path(id.to_string()), "", user,
8✔
540
                                                   RequestTokenType::RefreshToken,
8✔
541
                                                   handle_default_response(std::move(completion)));
8✔
542
}
8✔
543

544
void App::UserAPIKeyProviderClient::enable_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
545
                                                   UniqueFunction<void(Optional<AppError>)>&& completion)
546
{
8✔
547
    m_auth_request_client.do_authenticated_request(
8✔
548
        HttpMethod::put, url_for_path(util::format("%1/enable", id.to_string())), "", user,
8✔
549
        RequestTokenType::RefreshToken, handle_default_response(std::move(completion)));
8✔
550
}
8✔
551

552
void App::UserAPIKeyProviderClient::disable_api_key(const realm::ObjectId& id, const std::shared_ptr<User>& user,
553
                                                    UniqueFunction<void(Optional<AppError>)>&& completion)
554
{
8✔
555
    m_auth_request_client.do_authenticated_request(
8✔
556
        HttpMethod::put, url_for_path(util::format("%1/disable", id.to_string())), "", user,
8✔
557
        RequestTokenType::RefreshToken, handle_default_response(std::move(completion)));
8✔
558
}
8✔
559
// MARK: - App
560

561
// The user cache can have an expired pointer to an object if another thread is
562
// currently waiting for the mutex so that it can unregister the object, which
563
// will result in shared_from_this() throwing. We could instead do
564
// `weak_from_this().lock()`, but that is more expensive in the much more common
565
// case where the pointer is valid.
566
//
567
// Storing weak_ptrs in m_user would also avoid this problem, but would introduce
568
// a different one where the natural way to use the users could result in us
569
// trying to release the final strong reference while holding the lock, which
570
// would lead to a deadlock
571
static std::shared_ptr<User> try_lock(User& user)
572
{
3,785✔
573
    try {
3,785✔
574
        return user.shared_from_this();
3,785✔
575
    }
3,785✔
UNCOV
576
    catch (const std::bad_weak_ptr&) {
×
UNCOV
577
        return nullptr;
×
UNCOV
578
    }
×
579
}
3,785✔
580

581
std::shared_ptr<User> App::get_user_for_id(const std::string& user_id)
582
{
9,251✔
583
    if (auto& user = m_users[user_id]) {
9,251✔
584
        if (auto locked = try_lock(*user)) {
50✔
585
            return locked;
50✔
586
        }
50✔
587
    }
9,201✔
588
    return User::make(shared_from_this(), user_id);
9,201✔
589
}
9,201✔
590

591
void App::user_data_updated(const std::string& user_id)
592
{
4,720✔
593
    if (auto it = m_users.find(user_id); it != m_users.end()) {
4,720✔
594
        it->second->update_backing_data(m_metadata_store->get_user(user_id));
6✔
595
    }
6✔
596
}
4,720✔
597

598
std::shared_ptr<User> App::current_user()
599
{
8,244✔
600
    util::CheckedLockGuard lock(m_user_mutex);
8,244✔
601
    if (m_current_user && m_current_user->is_logged_in()) {
8,244✔
602
        if (auto user = try_lock(*m_current_user)) {
3,727✔
603
            return user;
3,727✔
604
        }
3,727✔
605
    }
4,517✔
606
    if (auto user_id = m_metadata_store->get_current_user(); !user_id.empty()) {
4,517✔
607
        auto user = get_user_for_id(user_id);
4,479✔
608
        m_current_user = user.get();
4,479✔
609
        return user;
4,479✔
610
    }
4,479✔
611
    return nullptr;
38✔
612
}
38✔
613

614
std::shared_ptr<User> App::get_existing_logged_in_user(std::string_view user_id)
UNCOV
615
{
×
UNCOV
616
    util::CheckedLockGuard lock(m_user_mutex);
×
UNCOV
617
    if (auto it = m_users.find(std::string(user_id)); it != m_users.end() && it->second->is_logged_in()) {
×
UNCOV
618
        if (auto user = try_lock(*it->second)) {
×
UNCOV
619
            return user;
×
UNCOV
620
        }
×
UNCOV
621
    }
×
UNCOV
622
    if (m_metadata_store->has_logged_in_user(user_id)) {
×
UNCOV
623
        return User::make(shared_from_this(), user_id);
×
UNCOV
624
    }
×
UNCOV
625
    return nullptr;
×
UNCOV
626
}
×
627

628
std::string App::get_base_url() const
629
{
76✔
630
    util::CheckedLockGuard guard(m_route_mutex);
76✔
631
    return m_base_url;
76✔
632
}
76✔
633

634
void App::update_base_url(std::string_view new_base_url, UniqueFunction<void(Optional<AppError>)>&& completion)
635
{
12✔
636
    if (new_base_url.empty()) {
12✔
637
        // Treat an empty string the same as requesting the default base url
3✔
638
        new_base_url = App::default_base_url();
6✔
639
        log_debug("App::update_base_url: empty => %1", new_base_url);
6✔
640
    }
6✔
641
    else {
6✔
642
        log_debug("App::update_base_url: %1", new_base_url);
6✔
643
    }
6✔
644

6✔
645
    // Validate the new base_url
6✔
646
    util::Uri::parse(new_base_url);
12✔
647

6✔
648
    bool update_not_needed;
12✔
649
    {
12✔
650
        util::CheckedLockGuard guard(m_route_mutex);
12✔
651
        // Update the location if the base_url is different or a location update is already needed
6✔
652
        m_location_updated = (new_base_url == m_base_url) && m_location_updated;
12!
653
        update_not_needed = m_location_updated;
12✔
654
    }
12✔
655
    // If the new base_url is the same as the current base_url and the location has already been updated,
6✔
656
    // then we're done
6✔
657
    if (update_not_needed) {
12✔
UNCOV
658
        completion(util::none);
×
UNCOV
659
        return;
×
UNCOV
660
    }
×
661

6✔
662
    // Otherwise, request the location information at the new base URL
6✔
663
    request_location(std::move(completion), std::string(new_base_url));
12✔
664
}
12✔
665

666
std::vector<std::shared_ptr<User>> App::all_users()
667
{
82✔
668
    util::CheckedLockGuard lock(m_user_mutex);
82✔
669
    auto user_ids = m_metadata_store->get_all_users();
82✔
670
    std::vector<std::shared_ptr<User>> users;
82✔
671
    users.reserve(user_ids.size());
82✔
672
    for (auto& user_id : user_ids) {
67✔
673
        users.push_back(get_user_for_id(user_id));
52✔
674
    }
52✔
675
    return users;
82✔
676
}
82✔
677

678
void App::get_profile(const std::shared_ptr<User>& user,
679
                      UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
680
{
4,728✔
681
    do_authenticated_request(
4,728✔
682
        HttpMethod::get, url_for_path("/auth/profile"), "", user, RequestTokenType::AccessToken,
4,728✔
683
        [completion = std::move(completion), self = shared_from_this(), user,
4,728✔
684
         this](const Response& profile_response) {
4,728✔
685
            if (auto error = AppUtils::check_for_errors(profile_response)) {
4,728✔
686
                return completion(nullptr, std::move(error));
×
687
            }
×
688

2,353✔
689
            try {
4,728✔
690
                auto profile_json = parse<BsonDocument>(profile_response.body);
4,728✔
691
                auto identities_json = get<BsonArray>(profile_json, "identities");
4,728✔
692

2,353✔
693
                std::vector<UserIdentity> identities;
4,728✔
694
                identities.reserve(identities_json.size());
4,728✔
695
                for (auto& identity_json : identities_json) {
4,762✔
696
                    auto doc = as<BsonDocument>(identity_json);
4,762✔
697
                    identities.push_back({get<std::string>(doc, "id"), get<std::string>(doc, "provider_type")});
4,762✔
698
                }
4,762✔
699

2,353✔
700
                if (auto data = m_metadata_store->get_user(user->user_id())) {
4,728✔
701
                    data->identities = std::move(identities);
4,728✔
702
                    data->profile = UserProfile(get<BsonDocument>(profile_json, "data"));
4,728✔
703
                    m_metadata_store->update_user(user->user_id(), *data);
4,728✔
704
                    user->update_backing_data(std::move(data));
4,728✔
705
                }
4,728✔
706
            }
4,728✔
707
            catch (const AppError& err) {
2,353✔
UNCOV
708
                return completion(nullptr, err);
×
UNCOV
709
            }
×
710

2,353✔
711
            return completion(user, {});
4,728✔
712
        });
4,728✔
713
}
4,728✔
714

715
void App::attach_auth_options(BsonDocument& body)
716
{
4,752✔
717
    log_debug("App: version info: platform: %1  version: %2 - sdk: %3 - sdk version: %4 - core version: %5",
4,752✔
718
              util::get_library_platform(), m_config.device_info.platform_version, m_config.device_info.sdk,
4,752✔
719
              m_config.device_info.sdk_version, REALM_VERSION_STRING);
4,752✔
720

2,365✔
721
    BsonDocument options;
4,752✔
722
    options["appId"] = m_config.app_id;
4,752✔
723
    options["platform"] = util::get_library_platform();
4,752✔
724
    options["platformVersion"] = m_config.device_info.platform_version;
4,752✔
725
    options["sdk"] = m_config.device_info.sdk;
4,752✔
726
    options["sdkVersion"] = m_config.device_info.sdk_version;
4,752✔
727
    options["cpuArch"] = util::get_library_cpu_arch();
4,752✔
728
    options["deviceName"] = m_config.device_info.device_name;
4,752✔
729
    options["deviceVersion"] = m_config.device_info.device_version;
4,752✔
730
    options["frameworkName"] = m_config.device_info.framework_name;
4,752✔
731
    options["frameworkVersion"] = m_config.device_info.framework_version;
4,752✔
732
    options["coreVersion"] = REALM_VERSION_STRING;
4,752✔
733
    options["bundleId"] = m_config.device_info.bundle_id;
4,752✔
734

2,365✔
735
    body["options"] = BsonDocument({{"device", options}});
4,752✔
736
}
4,752✔
737

738
void App::log_in_with_credentials(const AppCredentials& credentials, const std::shared_ptr<User>& linking_user,
739
                                  UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
740
{
4,760✔
741
    if (would_log(util::Logger::Level::debug)) {
4,760✔
742
        auto app_info = util::format("app_id: %1", m_config.app_id);
4,760✔
743
        log_debug("App: log_in_with_credentials: %1", app_info);
4,760✔
744
    }
4,760✔
745
    // if we try logging in with an anonymous user while there
2,369✔
746
    // is already an anonymous session active, reuse it
2,369✔
747
    std::shared_ptr<User> anon_user;
4,760✔
748
    if (credentials.provider() == AuthProvider::ANONYMOUS) {
4,760✔
749
        util::CheckedLockGuard lock(m_user_mutex);
86✔
750
        for (auto& [_, user] : m_users) {
50✔
751
            if (user->is_anonymous()) {
14✔
752
                anon_user = try_lock(*user);
8✔
753
                if (!anon_user)
8✔
UNCOV
754
                    continue;
×
755
                m_current_user = user;
8✔
756
                m_metadata_store->set_current_user(user->user_id());
8✔
757
                break;
8✔
758
            }
8✔
759
        }
14✔
760
    }
86✔
761

2,369✔
762
    if (anon_user) {
4,760✔
763
        emit_change_to_subscribers(*this);
8✔
764
        completion(anon_user, util::none);
8✔
765
        return;
8✔
766
    }
8✔
767

2,365✔
768
    if (linking_user) {
4,752✔
769
        util::CheckedLockGuard lock(m_user_mutex);
10✔
770
        if (!verify_user_present(linking_user)) {
10✔
UNCOV
771
            return completion(nullptr, AppError(ErrorCodes::ClientUserNotFound, "The specified user was not found."));
×
UNCOV
772
        }
×
773
    }
4,752✔
774

2,365✔
775
    // construct the route
2,365✔
776
    std::string route = util::format("%1/providers/%2/login%3", auth_route(), credentials.provider_as_string(),
4,752✔
777
                                     linking_user ? "?link=true" : "");
4,747✔
778

2,365✔
779
    BsonDocument body = credentials.serialize_as_bson();
4,752✔
780
    attach_auth_options(body);
4,752✔
781

2,365✔
782
    do_request(
4,752✔
783
        make_request(HttpMethod::post, std::move(route), linking_user, RequestTokenType::AccessToken,
4,752✔
784
                     Bson(body).to_string()),
4,752✔
785
        [completion = std::move(completion), credentials, linking_user, self = shared_from_this(),
4,752✔
786
         this](auto&&, const Response& response) mutable {
4,752✔
787
            if (auto error = AppUtils::check_for_errors(response)) {
4,752✔
788
                log_error("App: log_in_with_credentials failed: %1 message: %2", response.http_status_code,
20✔
789
                          error->what());
20✔
790
                return completion(nullptr, std::move(error));
20✔
791
            }
20✔
792

2,355✔
793
            std::shared_ptr<User> user = linking_user;
4,732✔
794
            try {
4,732✔
795
                auto json = parse<BsonDocument>(response.body);
4,732✔
796
                if (linking_user) {
4,732✔
797
                    if (auto user_data = m_metadata_store->get_user(linking_user->user_id())) {
10✔
798
                        user_data->access_token = RealmJWT(get<std::string>(json, "access_token"));
10✔
799
                        // maybe a callback for this?
5✔
800
                        m_metadata_store->update_user(linking_user->user_id(), *user_data);
10✔
801
                        linking_user->update_backing_data(std::move(user_data));
10✔
802
                    }
10✔
803
                }
10✔
804
                else {
4,722✔
805
                    auto user_id = get<std::string>(json, "user_id");
4,722✔
806
                    m_metadata_store->create_user(user_id, get<std::string>(json, "refresh_token"),
4,722✔
807
                                                  get<std::string>(json, "access_token"),
4,722✔
808
                                                  get<std::string>(json, "device_id"));
4,722✔
809
                    util::CheckedLockGuard lock(m_user_mutex);
4,722✔
810
                    user_data_updated(user_id); // FIXME: needs to be callback from metadata store
4,722✔
811
                    user = get_user_for_id(user_id);
4,722✔
812
                }
4,722✔
813
            }
4,732✔
814
            catch (const AppError& e) {
2,356✔
815
                return completion(nullptr, e);
2✔
816
            }
2✔
817
            // If the user has not been logged in, then there is a problem with the token
2,354✔
818
            if (!user->is_logged_in()) {
4,730✔
819
                return completion(nullptr,
2✔
820
                                  AppError(ErrorCodes::BadToken, "Could not log in user: received malformed JWT"));
2✔
821
            }
2✔
822
            switch_user(user);
4,728✔
823
            get_profile(user, std::move(completion));
4,728✔
824
        },
4,728✔
825
        false);
4,752✔
826
}
4,752✔
827

828
void App::log_in_with_credentials(
829
    const AppCredentials& credentials,
830
    util::UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
831
{
4,750✔
832
    App::log_in_with_credentials(credentials, nullptr, std::move(completion));
4,750✔
833
}
4,750✔
834

835
void App::log_out(const std::shared_ptr<User>& user, SyncUser::State new_state,
836
                  UniqueFunction<void(Optional<AppError>)>&& completion)
837
{
52✔
838
    if (!user || user->state() == new_state || user->state() == SyncUser::State::Removed) {
52✔
839
        if (completion) {
6✔
840
            completion(util::none);
4✔
841
        }
4✔
842
        return;
6✔
843
    }
6✔
844

22✔
845
    auto request =
46✔
846
        make_request(HttpMethod::del, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, "");
46✔
847

22✔
848
    m_metadata_store->log_out(user->user_id(), new_state);
46✔
849
    user->update_backing_data(m_metadata_store->get_user(user->user_id()));
46✔
850

22✔
851
    do_request(std::move(request),
46✔
852
               [self = shared_from_this(), completion = std::move(completion)](auto&&, const Response& response) {
46✔
853
                   auto error = AppUtils::check_for_errors(response);
46✔
854
                   if (!error) {
46✔
855
                       self->emit_change_to_subscribers(*self);
46✔
856
                   }
46✔
857
                   if (completion) {
46✔
858
                       completion(error);
41✔
859
                   }
41✔
860
               });
46✔
861
}
46✔
862

863
void App::log_out(const std::shared_ptr<User>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
864
{
37✔
865
    auto new_state = user && user->is_anonymous() ? SyncUser::State::Removed : SyncUser::State::LoggedOut;
37✔
866
    log_out(user, new_state, std::move(completion));
37✔
867
}
37✔
868

869
void App::log_out(UniqueFunction<void(Optional<AppError>)>&& completion)
870
{
18✔
871
    log_out(current_user(), std::move(completion));
18✔
872
}
18✔
873

874
bool App::verify_user_present(const std::shared_ptr<User>& user) const
875
{
4,769✔
876
    for (auto& [_, u] : m_users) {
4,771✔
877
        if (u == user.get())
4,771✔
878
            return true;
4,769✔
879
    }
4,771✔
880
    return false;
2,373✔
881
}
4,769✔
882

883
void App::switch_user(const std::shared_ptr<User>& user)
884
{
4,736✔
885
    if (!user || user->state() != SyncUser::State::LoggedIn) {
4,736✔
886
        throw AppError(ErrorCodes::ClientUserNotLoggedIn, "User is no longer valid or is logged out");
2✔
887
    }
2✔
888
    util::CheckedLockGuard lock(m_user_mutex);
4,734✔
889
    if (!verify_user_present(user)) {
4,734✔
UNCOV
890
        throw AppError(ErrorCodes::ClientUserNotFound, "User does not exist");
×
UNCOV
891
    }
×
892

2,356✔
893
    m_current_user = user.get();
4,734✔
894
    m_metadata_store->set_current_user(user->user_id());
4,734✔
895
    emit_change_to_subscribers(*this);
4,734✔
896
}
4,734✔
897

898
void App::remove_user(const std::shared_ptr<User>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
899
{
21✔
900
    if (!user || user->state() == SyncUser::State::Removed) {
21✔
901
        return completion(AppError(ErrorCodes::ClientUserNotFound, "User has already been removed"));
4✔
902
    }
4✔
903

8✔
904
    {
17✔
905
        util::CheckedLockGuard lock(m_user_mutex);
17✔
906
        if (!verify_user_present(user)) {
17✔
UNCOV
907
            return completion(AppError(ErrorCodes::ClientUserNotFound, "No user has been found"));
×
UNCOV
908
        }
×
909
    }
17✔
910

8✔
911
    if (user->is_logged_in()) {
17✔
912
        log_out(
15✔
913
            user, SyncUser::State::Removed,
15✔
914
            [user, completion = std::move(completion), self = shared_from_this()](const Optional<AppError>& error) {
15✔
915
                user->update_backing_data(std::nullopt);
15✔
916
                if (completion) {
15✔
917
                    completion(error);
14✔
918
                }
14✔
919
            });
15✔
920
    }
15✔
921
    else {
2✔
922
        m_metadata_store->log_out(user->user_id(), SyncUser::State::Removed);
2✔
923
        user->update_backing_data(std::nullopt);
2✔
924
        if (completion) {
2✔
925
            completion(std::nullopt);
2✔
926
        }
2✔
927
    }
2✔
928
}
17✔
929

930
void App::delete_user(const std::shared_ptr<User>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
931
{
12✔
932
    if (!user) {
12✔
UNCOV
933
        return completion(AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found."));
×
934
    }
×
935
    if (user->state() != SyncUser::State::LoggedIn) {
12✔
936
        return completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "User must be logged in to be deleted."));
4✔
937
    }
4✔
938

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

4✔
946
    do_authenticated_request(
8✔
947
        HttpMethod::del, url_for_path("/auth/delete"), "", user, RequestTokenType::AccessToken,
8✔
948
        [self = shared_from_this(), completion = std::move(completion), user, this](const Response& response) {
8✔
949
            auto error = AppUtils::check_for_errors(response);
8✔
950
            if (!error) {
8✔
951
                auto user_id = user->user_id();
8✔
952
                user->detach_and_tear_down();
8✔
953
                m_metadata_store->delete_user(*m_file_manager, user_id);
8✔
954
                emit_change_to_subscribers(*self);
8✔
955
            }
8✔
956
            completion(std::move(error));
8✔
957
        });
8✔
958
}
8✔
959

960
void App::link_user(const std::shared_ptr<User>& user, const AppCredentials& credentials,
961
                    UniqueFunction<void(const std::shared_ptr<User>&, Optional<AppError>)>&& completion)
962
{
12✔
963
    if (!user) {
12✔
UNCOV
964
        return completion(nullptr,
×
UNCOV
965
                          AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found."));
×
UNCOV
966
    }
×
967
    if (user->state() != SyncUser::State::LoggedIn) {
12✔
968
        return completion(nullptr,
2✔
969
                          AppError(ErrorCodes::ClientUserNotLoggedIn, "The specified user is not logged in."));
2✔
970
    }
2✔
971
    if (credentials.provider() == AuthProvider::ANONYMOUS) {
10✔
UNCOV
972
        return completion(nullptr, AppError(ErrorCodes::ClientUserAlreadyNamed,
×
UNCOV
973
                                            "Cannot add anonymous credentials to an existing user."));
×
UNCOV
974
    }
×
975

5✔
976
    log_in_with_credentials(credentials, user, std::move(completion));
10✔
977
}
10✔
978

979
void App::refresh_custom_data(const std::shared_ptr<User>& user,
980
                              UniqueFunction<void(Optional<AppError>)>&& completion)
981
{
14✔
982
    refresh_access_token(user, false, std::move(completion));
14✔
983
}
14✔
984

985
void App::refresh_custom_data(const std::shared_ptr<User>& user, bool update_location,
986
                              UniqueFunction<void(Optional<AppError>)>&& completion)
987
{
10✔
988
    refresh_access_token(user, update_location, std::move(completion));
10✔
989
}
10✔
990

991
std::string App::url_for_path(const std::string& path = "") const
992
{
4,966✔
993
    util::CheckedLockGuard guard(m_route_mutex);
4,966✔
994
    return util::format("%1%2", m_base_route, path);
4,966✔
995
}
4,966✔
996

997
std::string App::get_app_route(const Optional<std::string>& hostname) const
998
{
4,371✔
999
    if (hostname) {
4,371✔
1000
        return util::format("%1%2%3/%4", *hostname, s_base_path, s_app_path, m_config.app_id);
118✔
1001
    }
118✔
1002
    return m_app_route;
4,253✔
1003
}
4,253✔
1004

1005
void App::request_location(UniqueFunction<void(std::optional<AppError>)>&& completion,
1006
                           std::optional<std::string>&& new_hostname, std::optional<std::string>&& redir_location,
1007
                           int redirect_count)
1008
{
4,371✔
1009
    // Request the new location information at the new base url hostname; or redir response location if a redirect
2,177✔
1010
    // occurred during the initial location request. redirect_count is used to track the number of sequential
2,177✔
1011
    // redirect responses received during the location update and return an error if this count exceeds
2,177✔
1012
    // max_http_redirects. If neither new_hostname nor redir_location is provided, the current value of m_base_url
2,177✔
1013
    // will be used.
2,177✔
1014
    std::string app_route;
4,371✔
1015
    std::string base_url;
4,371✔
1016
    {
4,371✔
1017
        util::CheckedUniqueLock lock(m_route_mutex);
4,371✔
1018
        // Skip if the location info has already been initialized and a new hostname is not provided
2,177✔
1019
        if (!new_hostname && !redir_location && m_location_updated) {
4,371✔
1020
            // Release the lock before calling the completion function
UNCOV
1021
            lock.unlock();
×
UNCOV
1022
            completion(util::none);
×
UNCOV
1023
            return;
×
UNCOV
1024
        }
×
1025
        base_url = new_hostname.value_or(m_base_url);
4,371✔
1026
        // If this is for a redirect after querying new_hostname, then use the redirect location
2,177✔
1027
        if (redir_location)
4,371✔
1028
            app_route = get_app_route(redir_location);
106✔
1029
        // If this is querying the new_hostname, then use that location
2,124✔
1030
        else if (new_hostname)
4,265✔
1031
            app_route = get_app_route(new_hostname);
12✔
1032
        else
4,253✔
1033
            app_route = get_app_route();
4,253✔
1034
        REALM_ASSERT(!app_route.empty());
4,371✔
1035
    }
4,371✔
1036

2,177✔
1037
    Request req;
4,371✔
1038
    req.method = HttpMethod::get;
4,371✔
1039
    req.url = util::format("%1/location", app_route);
4,371✔
1040
    req.timeout_ms = m_request_timeout_ms;
4,371✔
1041

2,177✔
1042
    log_debug("App: request location: %1", req.url);
4,371✔
1043

2,177✔
1044
    m_config.transport->send_request_to_server(
4,371✔
1045
        req, [self = shared_from_this(), completion = std::move(completion), base_url = std::move(base_url),
4,371✔
1046
              redirect_count](const Response& response) mutable {
4,371✔
1047
            // Check to see if a redirect occurred
2,177✔
1048
            if (AppUtils::is_redirect_status_code(response.http_status_code)) {
4,371✔
1049
                // Make sure we don't do too many redirects (max_http_redirects (20) is an arbitrary number)
57✔
1050
                if (redirect_count >= s_max_http_redirects) {
114✔
1051
                    completion(AppError{ErrorCodes::ClientTooManyRedirects,
4✔
1052
                                        util::format("number of redirections exceeded %1", s_max_http_redirects),
4✔
1053
                                        {},
4✔
1054
                                        response.http_status_code});
4✔
1055
                    return;
4✔
1056
                }
4✔
1057
                // Handle the redirect response when requesting the location - extract the
55✔
1058
                // new location header field and resend the request.
55✔
1059
                auto redir_location = AppUtils::extract_redir_location(response.headers);
110✔
1060
                if (!redir_location) {
110✔
1061
                    // Location not found in the response, pass error response up the chain
2✔
1062
                    completion(AppError{ErrorCodes::ClientRedirectError,
4✔
1063
                                        "Redirect response missing location header",
4✔
1064
                                        {},
4✔
1065
                                        response.http_status_code});
4✔
1066
                    return;
4✔
1067
                }
4✔
1068
                // try to request the location info at the new location in the redirect response
53✔
1069
                // retry_count is passed in to track the number of subsequent redirection attempts
53✔
1070
                self->request_location(std::move(completion), std::move(base_url), std::move(redir_location),
106✔
1071
                                       redirect_count + 1);
106✔
1072
                return;
106✔
1073
            }
106✔
1074

2,120✔
1075
            // Location request was successful - update the location info
2,120✔
1076
            auto update_response = self->update_location(response, base_url);
4,257✔
1077
            if (update_response) {
4,257✔
1078
                self->log_error("App: request location failed (%1%2): %3", update_response->code_string(),
22✔
1079
                                update_response->additional_status_code
22✔
1080
                                    ? util::format(" %1", *update_response->additional_status_code)
22✔
1081
                                    : "",
11✔
1082
                                update_response->reason());
22✔
1083
            }
22✔
1084
            completion(update_response);
4,257✔
1085
        });
4,257✔
1086
}
4,371✔
1087

1088
std::optional<AppError> App::update_location(const Response& response, const std::string& base_url)
1089
{
4,257✔
1090
    // Validate the location info response for errors and update the stored location info if it is
2,120✔
1091
    // a valid response. base_url is the new hostname or m_base_url value when request_location()
2,120✔
1092
    // was called.
2,120✔
1093

2,120✔
1094
    if (auto error = AppUtils::check_for_errors(response)) {
4,257✔
1095
        return error;
22✔
1096
    }
22✔
1097

2,109✔
1098
    // Update the location info with the data from the response
2,109✔
1099
    try {
4,235✔
1100
        auto json = parse<BsonDocument>(response.body);
4,235✔
1101
        auto hostname = get<std::string>(json, "hostname");
4,235✔
1102
        auto ws_hostname = get<std::string>(json, "ws_hostname");
4,235✔
1103
        std::optional<std::string> sync_route;
4,235✔
1104
        read_field(json, "sync_route", sync_route);
4,235✔
1105

2,109✔
1106
        util::CheckedLockGuard guard(m_route_mutex);
4,235✔
1107
        // Update the local hostname and path information
2,109✔
1108
        update_hostname(hostname, ws_hostname, base_url);
4,235✔
1109
        m_location_updated = true;
4,235✔
1110
        if (!sync_route) {
4,235✔
1111
            sync_route = make_sync_route();
4,227✔
1112
        }
4,227✔
1113
        m_sync_manager->set_sync_route(*sync_route, true);
4,235✔
1114
    }
4,235✔
1115
    catch (const AppError& ex) {
2,109✔
UNCOV
1116
        return ex;
×
UNCOV
1117
    }
×
1118
    return util::none;
4,235✔
1119
}
4,235✔
1120

1121
void App::update_location_and_resend(std::unique_ptr<Request>&& request, IntermediateCompletion&& completion,
1122
                                     Optional<std::string>&& redir_location)
1123
{
4,253✔
1124
    // Update the location information if a redirect response was received or m_location_updated == false
2,118✔
1125
    // and then send the request to the server with request.url updated to the new AppServices hostname.
2,118✔
1126
    request_location(
4,253✔
1127
        [completion = std::move(completion), request = std::move(request),
4,253✔
1128
         self = shared_from_this()](Optional<AppError> error) mutable {
4,253✔
1129
            if (error) {
4,253✔
1130
                // Operation failed, pass it up the chain
14✔
1131
                return completion(std::move(request), AppUtils::make_apperror_response(*error));
28✔
1132
            }
28✔
1133

2,104✔
1134
            // If the location info was updated, update the original request to point
2,104✔
1135
            // to the new location URL.
2,104✔
1136
            auto url = util::Uri::parse(request->url);
4,225✔
1137
            request->url =
4,225✔
1138
                util::format("%1%2%3%4", self->get_host_url(), url.get_path(), url.get_query(), url.get_frag());
4,225✔
1139

2,104✔
1140
            self->log_debug("App: send_request(after location update): %1 %2", request->method, request->url);
4,225✔
1141
            // Retry the original request with the updated url
2,104✔
1142
            auto& request_ref = *request;
4,225✔
1143
            self->m_config.transport->send_request_to_server(
4,225✔
1144
                request_ref, [self = std::move(self), completion = std::move(completion),
4,225✔
1145
                              request = std::move(request)](const Response& response) mutable {
4,225✔
1146
                    self->check_for_redirect_response(std::move(request), response, std::move(completion));
4,225✔
1147
                });
4,225✔
1148
        },
4,225✔
1149
        // The base_url is not changing for this request
2,118✔
1150
        util::none, std::move(redir_location));
4,253✔
1151
}
4,253✔
1152

1153
void App::post(std::string&& route, UniqueFunction<void(Optional<AppError>)>&& completion, const BsonDocument& body)
1154
{
4,684✔
1155
    do_request(
4,684✔
1156
        make_request(HttpMethod::post, std::move(route), nullptr, RequestTokenType::NoAuth, Bson(body).to_string()),
4,684✔
1157
        [completion = std::move(completion)](auto&&, const Response& response) {
4,684✔
1158
            completion(AppUtils::check_for_errors(response));
4,684✔
1159
        });
4,684✔
1160
}
4,684✔
1161

1162
void App::do_request(std::unique_ptr<Request>&& request, IntermediateCompletion&& completion, bool update_location)
1163
{
14,950✔
1164
    // Verify the request URL to make sure it is valid
7,354✔
1165
    util::Uri::parse(request->url);
14,950✔
1166

7,354✔
1167
    // Refresh the location info when app is created or when requested (e.g. after a websocket redirect)
7,354✔
1168
    // to ensure the http and websocket URL information is up to date.
7,354✔
1169
    {
14,950✔
1170
        util::CheckedUniqueLock lock(m_route_mutex);
14,950✔
1171
        if (update_location) {
14,950✔
1172
            // If requesting a location update, force the location to be updated before sending the request.
17✔
1173
            m_location_updated = false;
34✔
1174
        }
34✔
1175
        if (!m_location_updated) {
14,950✔
1176
            lock.unlock();
4,253✔
1177
            // Location info needs to be requested, update the location info and then send the request
2,118✔
1178
            update_location_and_resend(std::move(request), std::move(completion));
4,253✔
1179
            return;
4,253✔
1180
        }
4,253✔
1181
    }
10,697✔
1182

5,236✔
1183
    log_debug("App: do_request: %1 %2", request->method, request->url);
10,697✔
1184
    // If location info has already been updated, then send the request directly
5,236✔
1185
    auto& request_ref = *request;
10,697✔
1186
    m_config.transport->send_request_to_server(
10,697✔
1187
        request_ref, [self = shared_from_this(), completion = std::move(completion),
10,697✔
1188
                      request = std::move(request)](const Response& response) mutable {
10,697✔
1189
            self->check_for_redirect_response(std::move(request), response, std::move(completion));
10,697✔
1190
        });
10,697✔
1191
}
10,697✔
1192

1193
void App::check_for_redirect_response(std::unique_ptr<Request>&& request, const Response& response,
1194
                                      IntermediateCompletion&& completion)
1195
{
14,922✔
1196
    // If this isn't a redirect response, then we're done
7,340✔
1197
    if (!AppUtils::is_redirect_status_code(response.http_status_code)) {
14,922✔
1198
        return completion(std::move(request), response);
14,922✔
1199
    }
14,922✔
1200

1201
    // Handle a redirect response when sending the original request - extract the location
1202
    // header field and resend the request.
UNCOV
1203
    auto redir_location = AppUtils::extract_redir_location(response.headers);
×
UNCOV
1204
    if (!redir_location) {
×
1205
        // Location not found in the response, pass error response up the chain
UNCOV
1206
        return completion(std::move(request),
×
UNCOV
1207
                          AppUtils::make_clienterror_response(ErrorCodes::ClientRedirectError,
×
UNCOV
1208
                                                              "Redirect response missing location header",
×
UNCOV
1209
                                                              response.http_status_code));
×
UNCOV
1210
    }
×
1211

1212
    // Request the location info at the new location - once this is complete, the original
1213
    // request will be sent to the new server
UNCOV
1214
    update_location_and_resend(std::move(request), std::move(completion), std::move(redir_location));
×
UNCOV
1215
}
×
1216

1217
void App::do_authenticated_request(HttpMethod method, std::string&& route, std::string&& body,
1218
                                   const std::shared_ptr<User>& user, RequestTokenType token_type,
1219
                                   util::UniqueFunction<void(const Response&)>&& completion)
1220
{
5,372✔
1221
    auto request = make_request(method, std::move(route), user, token_type, std::move(body));
5,372✔
1222
    do_request(std::move(request), [token_type, user, completion = std::move(completion), self = shared_from_this()](
5,372✔
1223
                                       std::unique_ptr<Request>&& request, const Response& response) mutable {
5,372✔
1224
        if (auto error = AppUtils::check_for_errors(response)) {
5,372✔
1225
            self->handle_auth_failure(std::move(*error), std::move(request), response, user, token_type,
66✔
1226
                                      std::move(completion));
66✔
1227
        }
66✔
1228
        else {
5,306✔
1229
            completion(response);
5,306✔
1230
        }
5,306✔
1231
    });
5,372✔
1232
}
5,372✔
1233

1234
void App::handle_auth_failure(const AppError& error, std::unique_ptr<Request>&& request, const Response& response,
1235
                              const std::shared_ptr<User>& user, RequestTokenType token_type,
1236
                              util::UniqueFunction<void(const Response&)>&& completion)
1237
{
66✔
1238
    // Only handle auth failures
33✔
1239
    if (*error.additional_status_code != 401) {
66✔
1240
        completion(response);
40✔
1241
        return;
40✔
1242
    }
40✔
1243

13✔
1244
    // If the refresh token is invalid then the user needs to be logged back
13✔
1245
    // in to be able to use it again
13✔
1246
    if (token_type == RequestTokenType::RefreshToken) {
26✔
1247
        if (user && user->is_logged_in()) {
18!
UNCOV
1248
            user->log_out();
×
UNCOV
1249
        }
×
1250
        completion(response);
18✔
1251
        return;
18✔
1252
    }
18✔
1253

4✔
1254
    // Otherwise we may be able to request a new access token and have the request succeed with that
4✔
1255
    refresh_access_token(user, false,
8✔
1256
                         [self = shared_from_this(), request = std::move(request), completion = std::move(completion),
8✔
1257
                          response = std::move(response), user](Optional<AppError>&& error) mutable {
8✔
1258
                             if (error) {
8✔
1259
                                 // pass the error back up the chain
2✔
1260
                                 completion(response);
4✔
1261
                                 return;
4✔
1262
                             }
4✔
1263

2✔
1264
                             // Reissue the request with the new access token
2✔
1265
                             request->headers = get_request_headers(user, RequestTokenType::AccessToken);
4✔
1266
                             self->do_request(std::move(request),
4✔
1267
                                              [completion = std::move(completion)](auto&&, auto& response) {
4✔
1268
                                                  completion(response);
4✔
1269
                                              });
4✔
1270
                         });
4✔
1271
}
8✔
1272

1273
/// MARK: - refresh access token
1274
void App::refresh_access_token(const std::shared_ptr<User>& user, bool update_location,
1275
                               util::UniqueFunction<void(Optional<AppError>)>&& completion)
1276
{
96✔
1277
    if (!user) {
96✔
1278
        completion(AppError(ErrorCodes::ClientUserNotFound, "No current user exists"));
2✔
1279
        return;
2✔
1280
    }
2✔
1281

47✔
1282
    if (!user->is_logged_in()) {
94✔
1283
        completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "The user is not logged in"));
2✔
1284
        return;
2✔
1285
    }
2✔
1286

46✔
1287
    log_debug("App: refresh_access_token: email: %1 %2", user->user_profile().email(),
92✔
1288
              update_location ? "(updating location)" : "");
75✔
1289

46✔
1290
    // If update_location is set, force the location info to be updated before sending the request
46✔
1291
    do_request(
92✔
1292
        make_request(HttpMethod::post, url_for_path("/auth/session"), user, RequestTokenType::RefreshToken, ""),
92✔
1293
        [completion = std::move(completion), self = shared_from_this(), user](auto&&, const Response& response) {
92✔
1294
            if (auto error = AppUtils::check_for_errors(response)) {
92✔
1295
                return completion(std::move(error));
46✔
1296
            }
46✔
1297

23✔
1298
            try {
46✔
1299
                auto json = parse<BsonDocument>(response.body);
46✔
1300
                RealmJWT access_token{get<std::string>(json, "access_token")};
46✔
1301
                if (auto data = self->m_metadata_store->get_user(user->user_id())) {
46✔
1302
                    data->access_token = access_token;
44✔
1303
                    self->m_metadata_store->update_user(user->user_id(), *data);
44✔
1304
                    user->update_backing_data(std::move(data));
44✔
1305
                }
44✔
1306
            }
46✔
1307
            catch (AppError& err) {
24✔
1308
                return completion(std::move(err));
2✔
1309
            }
2✔
1310

22✔
1311
            return completion(util::none);
44✔
1312
        },
44✔
1313
        update_location);
92✔
1314
}
92✔
1315

1316
std::string App::function_call_url_path() const
1317
{
552✔
1318
    util::CheckedLockGuard guard(m_route_mutex);
552✔
1319
    return util::format("%1/functions/call", m_app_route);
552✔
1320
}
552✔
1321

1322
void App::call_function(const std::shared_ptr<User>& user, const std::string& name, std::string_view args_ejson,
1323
                        const Optional<std::string>& service_name_opt,
1324
                        UniqueFunction<void(const std::string*, Optional<AppError>)>&& completion)
1325
{
544✔
1326
    auto service_name = service_name_opt ? *service_name_opt : "<none>";
540✔
1327
    if (would_log(util::Logger::Level::debug)) {
544✔
1328
        log_debug("App: call_function: %1 service_name: %2 args_bson: %3", name, service_name, args_ejson);
544✔
1329
    }
544✔
1330

185✔
1331
    auto args = util::format("{\"arguments\":%1,\"name\":%2%3}", args_ejson, nlohmann::json(name).dump(),
544✔
1332
                             service_name_opt ? (",\"service\":" + nlohmann::json(service_name).dump()) : "");
540✔
1333

185✔
1334
    do_authenticated_request(
544✔
1335
        HttpMethod::post, function_call_url_path(), std::move(args), user, RequestTokenType::AccessToken,
544✔
1336
        [self = shared_from_this(), name = name, service_name = std::move(service_name),
544✔
1337
         completion = std::move(completion)](const Response& response) {
544✔
1338
            if (auto error = AppUtils::check_for_errors(response)) {
544✔
1339
                self->log_error("App: call_function: %1 service_name: %2 -> %3 ERROR: %4", name, service_name,
18✔
1340
                                response.http_status_code, error->what());
18✔
1341
                return completion(nullptr, error);
18✔
1342
            }
18✔
1343
            completion(&response.body, util::none);
526✔
1344
        });
526✔
1345
}
544✔
1346

1347
void App::call_function(const std::shared_ptr<User>& user, const std::string& name, const BsonArray& args_bson,
1348
                        const Optional<std::string>& service_name,
1349
                        UniqueFunction<void(Optional<Bson>&&, Optional<AppError>)>&& completion)
1350
{
544✔
1351
    auto service_name2 = service_name ? *service_name : "<none>";
540✔
1352
    std::stringstream args_ejson;
544✔
1353
    args_ejson << "[";
544✔
1354
    bool not_first = false;
544✔
1355
    for (auto&& arg : args_bson) {
560✔
1356
        if (not_first)
560✔
1357
            args_ejson << ',';
16✔
1358
        args_ejson << arg.toJson();
560✔
1359
        not_first = true;
560✔
1360
    }
560✔
1361
    args_ejson << "]";
544✔
1362

185✔
1363
    call_function(user, name, std::move(args_ejson).str(), service_name,
544✔
1364
                  [self = shared_from_this(), name, service_name = std::move(service_name2),
544✔
1365
                   completion = std::move(completion)](const std::string* response, util::Optional<AppError> err) {
544✔
1366
                      if (err) {
544✔
1367
                          return completion({}, err);
18✔
1368
                      }
18✔
1369
                      if (!response) {
526✔
1370
                          return completion({}, AppError{ErrorCodes::AppUnknownError, "Empty response from server"});
×
1371
                      }
×
1372
                      util::Optional<Bson> body_as_bson;
526✔
1373
                      try {
526✔
1374
                          body_as_bson = bson::parse(*response);
526✔
1375
                          if (self->would_log(util::Logger::Level::debug)) {
526✔
1376
                              self->log_debug("App: call_function: %1 service_name: %2 - results: %3", name,
526✔
1377
                                              service_name, body_as_bson ? body_as_bson->to_string() : "<none>");
526✔
1378
                          }
526✔
1379
                      }
526✔
1380
                      catch (const std::exception& e) {
176✔
UNCOV
1381
                          self->log_error("App: call_function: %1 service_name: %2 - error parsing result: %3", name,
×
UNCOV
1382
                                          service_name, e.what());
×
UNCOV
1383
                          return completion(util::none, AppError(ErrorCodes::BadBsonParse, e.what()));
×
1384
                      };
526✔
1385
                      completion(std::move(body_as_bson), util::none);
526✔
1386
                  });
526✔
1387
}
544✔
1388

1389
void App::call_function(const std::shared_ptr<User>& user, const std::string& name, const BsonArray& args_bson,
1390
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1391
{
4✔
1392
    call_function(user, name, args_bson, util::none, std::move(completion));
4✔
1393
}
4✔
1394

1395
void App::call_function(const std::string& name, const BsonArray& args_bson,
1396
                        const Optional<std::string>& service_name,
1397
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
UNCOV
1398
{
×
UNCOV
1399
    call_function(current_user(), name, args_bson, service_name, std::move(completion));
×
UNCOV
1400
}
×
1401

1402
void App::call_function(const std::string& name, const BsonArray& args_bson,
1403
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1404
{
2✔
1405
    call_function(current_user(), name, args_bson, std::move(completion));
2✔
1406
}
2✔
1407

1408
Request App::make_streaming_request(const std::shared_ptr<User>& user, const std::string& name,
1409
                                    const BsonArray& args_bson, const Optional<std::string>& service_name) const
1410
{
8✔
1411
    auto args = BsonDocument{
8✔
1412
        {"arguments", args_bson},
8✔
1413
        {"name", name},
8✔
1414
    };
8✔
1415
    if (service_name) {
8✔
1416
        args["service"] = *service_name;
8✔
1417
    }
8✔
1418
    const auto args_json = Bson(args).to_string();
8✔
1419

4✔
1420
    auto args_base64 = std::string(util::base64_encoded_size(args_json.size()), '\0');
8✔
1421
    util::base64_encode(args_json, args_base64);
8✔
1422

4✔
1423
    auto url = function_call_url_path() + "?baas_request=" + util::uri_percent_encode(args_base64);
8✔
1424
    if (user) {
8✔
1425
        url += "&baas_at=";
2✔
1426
        url += user->access_token(); // doesn't need url encoding
2✔
1427
    }
2✔
1428

4✔
1429
    return Request{
8✔
1430
        HttpMethod::get,
8✔
1431
        url,
8✔
1432
        m_request_timeout_ms,
8✔
1433
        {{"Accept", "text/event-stream"}},
8✔
1434
    };
8✔
1435
}
8✔
1436

1437
std::unique_ptr<Request> App::make_request(HttpMethod method, std::string&& url, const std::shared_ptr<User>& user,
1438
                                           RequestTokenType token_type, std::string&& body) const
1439
{
14,946✔
1440
    auto request = std::make_unique<Request>();
14,946✔
1441
    request->method = method;
14,946✔
1442
    request->url = std::move(url);
14,946✔
1443
    request->body = std::move(body);
14,946✔
1444
    request->headers = get_request_headers(user, token_type);
14,946✔
1445
    request->timeout_ms = m_request_timeout_ms;
14,946✔
1446
    return request;
14,946✔
1447
}
14,946✔
1448

1449
PushClient App::push_notification_client(const std::string& service_name)
1450
{
10✔
1451
    return PushClient(service_name, m_config.app_id, std::shared_ptr<AuthRequestClient>(shared_from_this(), this));
10✔
1452
}
10✔
1453

1454
// MARK: - UserProvider
1455

1456
void App::register_sync_user(User& user)
1457
{
9,201✔
1458
    auto& tracked_user = m_users[user.user_id()];
9,201✔
1459
    REALM_ASSERT(!tracked_user || !tracked_user->weak_from_this().lock());
9,201!
1460
    tracked_user = &user;
9,201✔
1461
    user.update_backing_data(m_metadata_store->get_user(user.user_id()));
9,201✔
1462
}
9,201✔
1463

1464
void App::unregister_sync_user(User& user)
1465
{
9,201✔
1466
    util::CheckedLockGuard lock(m_user_mutex);
9,201✔
1467
    auto it = m_users.find(user.user_id());
9,201✔
1468
    REALM_ASSERT(it != m_users.end());
9,201✔
1469
    // If the user was requested while we were waiting for the lock, it may
4,580✔
1470
    // have already been replaced with a new instance for the same user id
4,580✔
1471
    if (it != m_users.end() && it->second == &user) {
9,201✔
1472
        m_users.erase(it);
9,201✔
1473
    }
9,201✔
1474
    if (m_current_user == &user) {
9,201✔
1475
        m_current_user = nullptr;
8,985✔
1476
    }
8,985✔
1477
}
9,201✔
1478

1479
bool App::immediately_run_file_actions(std::string_view realm_path)
1480
{
10✔
1481
    return m_metadata_store->immediately_run_file_actions(*m_file_manager, realm_path);
10✔
1482
}
10✔
1483

1484
std::string App::path_for_realm(const SyncConfig& config, std::optional<std::string> custom_file_name) const
1485
{
28✔
1486
    return m_file_manager->path_for_realm(config, std::move(custom_file_name));
28✔
1487
}
28✔
1488

1489
} // 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