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

realm / realm-core / 1691

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

push

Evergreen

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

Fix handling of users with multiple identities

95990 of 175908 branches covered (0.0%)

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

44 existing lines in 15 files now uncovered.

233732 of 256237 relevant lines covered (91.22%)

6741306.52 hits per line

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

93.3
/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 "external/json/json.hpp"
20
#include <realm/object-store/sync/app.hpp>
21

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

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

36
#include <sstream>
37
#include <string>
38

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

45
namespace {
46
// MARK: - Helpers
47

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

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

65
template <typename T>
66
T get(const BsonDocument& doc, const std::string& key)
67
{
10,240✔
68
    auto& raw = doc.entries();
10,240✔
69
    if (auto it = raw.find(key); it != raw.end()) {
10,240✔
70
        return as<T>(it->second);
10,236✔
71
    }
10,236✔
72
    throw_json_error(ErrorCodes::MissingJsonKey, key);
4✔
73
    return {};
4✔
74
}
4✔
75

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

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

94
template <typename T>
95
void read_field(const BsonDocument& data, const std::string& key, Optional<T>& value)
96
{
32✔
97
    auto& raw = data.entries();
32✔
98
    if (auto it = raw.find(key); it != raw.end()) {
32✔
99
        value = as<T>(it->second);
8✔
100
    }
8✔
101
}
32✔
102

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

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

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

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

142
enum class RequestTokenType { NoAuth, AccessToken, RefreshToken };
143

144
// generate the request headers for a HTTP call, by default it will generate headers with a refresh token if a user is
145
// passed
146
HttpHeaders get_request_headers(const std::shared_ptr<SyncUser>& with_user_authorization = nullptr,
147
                                RequestTokenType token_type = RequestTokenType::RefreshToken)
148
{
3,655✔
149
    HttpHeaders headers{{"Content-Type", "application/json;charset=utf-8"}, {"Accept", "application/json"}};
3,655✔
150

1,711✔
151
    if (with_user_authorization) {
3,655✔
152
        switch (token_type) {
1,629✔
153
            case RequestTokenType::NoAuth:
✔
154
                break;
×
155
            case RequestTokenType::AccessToken:
1,499✔
156
                headers.insert({"Authorization", util::format("Bearer %1", with_user_authorization->access_token())});
1,499✔
157
                break;
1,499✔
158
            case RequestTokenType::RefreshToken:
130✔
159
                headers.insert(
130✔
160
                    {"Authorization", util::format("Bearer %1", with_user_authorization->refresh_token())});
130✔
161
                break;
130✔
162
        }
3,655✔
163
    }
3,655✔
164
    return headers;
3,655✔
165
}
3,655✔
166

167
UniqueFunction<void(const Response&)> handle_default_response(UniqueFunction<void(Optional<AppError>)>&& completion)
168
{
982✔
169
    return [completion = std::move(completion)](const Response& response) {
982✔
170
        completion(AppUtils::check_for_errors(response));
982✔
171
    };
982✔
172
}
982✔
173

174
const static std::string default_base_url = "https://realm.mongodb.com";
175
const static std::string base_path = "/api/client/v2.0";
176
const static std::string app_path = "/app";
177
const static std::string auth_path = "/auth";
178
const static std::string sync_path = "/realm-sync";
179
const static uint64_t default_timeout_ms = 60000;
180
const static std::string username_password_provider_key = "local-userpass";
181
const static std::string user_api_key_provider_key_path = "api_keys";
182
const static int max_http_redirects = 20;
183
static util::FlatMap<std::string, util::FlatMap<std::string, SharedApp>> s_apps_cache; // app_id -> base_url -> app
184
std::mutex s_apps_mutex;
185

186
} // anonymous namespace
187

188
namespace realm {
189
namespace app {
190

191
App::Config::DeviceInfo::DeviceInfo()
192
    : platform(util::get_library_platform())
193
    , cpu_arch(util::get_library_cpu_arch())
194
    , core_version(REALM_VERSION_STRING)
195
{
4,141✔
196
}
4,141✔
197

198
App::Config::DeviceInfo::DeviceInfo(std::string a_platform_version, std::string an_sdk_version, std::string an_sdk,
199
                                    std::string a_device_name, std::string a_device_version,
200
                                    std::string a_framework_name, std::string a_framework_version,
201
                                    std::string a_bundle_id)
202
    : DeviceInfo()
203
{
519✔
204
    platform_version = a_platform_version;
519✔
205
    sdk_version = an_sdk_version;
519✔
206
    sdk = an_sdk;
519✔
207
    device_name = a_device_name;
519✔
208
    device_version = a_device_version;
519✔
209
    framework_name = a_framework_name;
519✔
210
    framework_version = a_framework_version;
519✔
211
    bundle_id = a_bundle_id;
519✔
212
}
519✔
213

214
SharedApp App::get_shared_app(const Config& config, const SyncClientConfig& sync_client_config)
215
{
10✔
216
    std::lock_guard<std::mutex> lock(s_apps_mutex);
10✔
217
    auto& app = s_apps_cache[config.app_id][config.base_url.value_or(default_base_url)];
10✔
218
    if (!app) {
10✔
219
        app = std::make_shared<App>(config);
6✔
220
        app->configure(sync_client_config);
6✔
221
    }
6✔
222
    return app;
10✔
223
}
10✔
224

225
SharedApp App::get_uncached_app(const Config& config, const SyncClientConfig& sync_client_config)
226
{
4,149✔
227
    auto app = std::make_shared<App>(config);
4,149✔
228
    app->configure(sync_client_config);
4,149✔
229
    return app;
4,149✔
230
}
4,149✔
231

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

5✔
238
        auto app_it = base_url ? apps_by_url.find(*base_url) : apps_by_url.begin();
9✔
239
        if (app_it != apps_by_url.end()) {
10✔
240
            return app_it->second;
8✔
241
        }
8✔
242
    }
2✔
243

1✔
244
    return nullptr;
2✔
245
}
2✔
246

247
void App::clear_cached_apps()
248
{
4,057✔
249
    std::lock_guard<std::mutex> lock(s_apps_mutex);
4,057✔
250
    s_apps_cache.clear();
4,057✔
251
}
4,057✔
252

253
void App::close_all_sync_sessions()
254
{
×
255
    std::lock_guard<std::mutex> lock(s_apps_mutex);
×
256
    for (auto& apps_by_url : s_apps_cache) {
×
257
        for (auto& app : apps_by_url.second) {
×
258
            app.second->sync_manager()->close_all_sessions();
×
259
        }
×
260
    }
×
261
}
×
262

263
App::App(const Config& config)
264
    : m_config(std::move(config))
265
    , m_base_url(m_config.base_url.value_or(default_base_url))
266
    , m_base_route(m_base_url + base_path)
267
    , m_app_route(m_base_route + app_path + "/" + m_config.app_id)
268
    , m_auth_route(m_app_route + auth_path)
269
    , m_request_timeout_ms(m_config.default_request_timeout_ms.value_or(default_timeout_ms))
270
{
4,157✔
271
#ifdef __EMSCRIPTEN__
272
    if (!m_config.transport) {
273
        m_config.transport = std::make_shared<_impl::EmscriptenNetworkTransport>();
274
    }
275
#endif
276
    REALM_ASSERT(m_config.transport);
4,157✔
277
    REALM_ASSERT(!m_config.device_info.platform.empty());
4,157✔
278

2,043✔
279
    if (m_config.device_info.platform_version.empty()) {
4,157✔
280
        throw InvalidArgument("You must specify the Platform Version in App::Config::device_info");
×
281
    }
×
282

2,043✔
283
    if (m_config.device_info.sdk.empty()) {
4,157✔
284
        throw InvalidArgument("You must specify the SDK Name in App::Config::device_info");
×
285
    }
×
286

2,043✔
287
    if (m_config.device_info.sdk_version.empty()) {
4,157✔
288
        throw InvalidArgument("You must specify the SDK Version in App::Config::device_info");
×
289
    }
×
290

2,043✔
291
    // change the scheme in the base url to ws from http to satisfy the sync client
2,043✔
292
    auto sync_route = make_sync_route(m_app_route);
4,157✔
293

2,043✔
294
    m_sync_manager = std::make_shared<SyncManager>();
4,157✔
295
}
4,157✔
296

297
App::~App() {}
4,157✔
298

299
void App::configure(const SyncClientConfig& sync_client_config)
300
{
4,155✔
301
    auto sync_route = make_sync_route(m_app_route);
4,155✔
302
    m_sync_manager->configure(shared_from_this(), sync_route, sync_client_config);
4,155✔
303
    if (auto metadata = m_sync_manager->app_metadata()) {
4,155✔
304
        // If there is app metadata stored, then set up the initial hostname/syncroute
2✔
305
        // using that info - it will be updated upon first request to the server
2✔
306
        update_hostname(metadata);
4✔
307
    }
4✔
308
    {
4,155✔
309
        std::lock_guard<std::mutex> lock(*m_route_mutex);
4,155✔
310
        // Always update the location after the app is configured/re-configured
2,042✔
311
        m_location_updated = false;
4,155✔
312
    }
4,155✔
313
}
4,155✔
314

315
bool App::init_logger()
316
{
8,743✔
317
    if (!m_logger_ptr) {
8,743✔
318
        m_logger_ptr = m_sync_manager->get_logger();
561✔
319
    }
561✔
320
    return bool(m_logger_ptr);
8,743✔
321
}
8,743✔
322

323
bool App::would_log(util::Logger::Level level)
324
{
1,944✔
325
    return init_logger() && m_logger_ptr->would_log(level);
1,944✔
326
}
1,944✔
327

328
template <class... Params>
329
void App::log_debug(const char* message, Params&&... params)
330
{
6,761✔
331
    if (init_logger()) {
6,761✔
332
        m_logger_ptr->log(util::Logger::Level::debug, message, std::forward<Params>(params)...);
6,753✔
333
    }
6,753✔
334
}
6,761✔
335

336
template <class... Params>
337
void App::log_error(const char* message, Params&&... params)
338
{
38✔
339
    if (init_logger()) {
38!
340
        m_logger_ptr->log(util::Logger::Level::error, message, std::forward<Params>(params)...);
38✔
341
    }
38✔
342
}
38✔
343

344
std::string App::make_sync_route(const std::string& http_app_route)
345
{
8,312✔
346
    // change the scheme in the base url from http to ws to satisfy the sync client URL
4,085✔
347
    auto sync_route = http_app_route + sync_path;
8,312✔
348
    size_t uri_scheme_start = sync_route.find("http");
8,312✔
349
    if (uri_scheme_start == 0)
8,312✔
350
        sync_route.replace(uri_scheme_start, 4, "ws");
8,312✔
351
    return sync_route;
8,312✔
352
}
8,312✔
353

354
void App::update_hostname(const util::Optional<SyncAppMetadata>& metadata)
355
{
533✔
356
    // Update url components based on new hostname value from the app metadata
258✔
357
    if (metadata) {
533✔
358
        update_hostname(metadata->hostname, metadata->ws_hostname);
533✔
359
    }
533✔
360
}
533✔
361

362
void App::update_hostname(const std::string& hostname, const Optional<std::string>& ws_hostname)
363
{
539✔
364
    // Update url components based on new hostname (and optional websocket hostname) values
261✔
365
    log_debug("App: update_hostname: %1 | %2", hostname, ws_hostname);
539✔
366
    REALM_ASSERT(m_sync_manager);
539✔
367
    std::lock_guard<std::mutex> lock(*m_route_mutex);
539✔
368
    m_base_route = (hostname.length() > 0 ? hostname : default_base_url) + base_path;
539✔
369
    std::string this_app_path = app_path + "/" + m_config.app_id;
539✔
370
    m_app_route = m_base_route + this_app_path;
539✔
371
    m_auth_route = m_app_route + auth_path;
539✔
372
    if (ws_hostname && ws_hostname->length() > 0) {
539✔
373
        m_sync_manager->set_sync_route(*ws_hostname + base_path + this_app_path + sync_path);
539✔
374
    }
539✔
375
    else {
×
376
        m_sync_manager->set_sync_route(make_sync_route(m_app_route));
×
377
    }
×
378
}
539✔
379

380
// MARK: - Template specializations
381

382
template <>
383
App::UsernamePasswordProviderClient App::provider_client<App::UsernamePasswordProviderClient>()
384
{
938✔
385
    return App::UsernamePasswordProviderClient(shared_from_this());
938✔
386
}
938✔
387

388
template <>
389
App::UserAPIKeyProviderClient App::provider_client<App::UserAPIKeyProviderClient>()
390
{
20✔
391
    return App::UserAPIKeyProviderClient(*this);
20✔
392
}
20✔
393

394
// MARK: - UsernamePasswordProviderClient
395

396
void App::UsernamePasswordProviderClient::register_email(const std::string& email, const std::string& password,
397
                                                         UniqueFunction<void(Optional<AppError>)>&& completion)
398
{
942✔
399
    m_parent->log_debug("App: register_email: %1", email);
942✔
400
    m_parent->post(util::format("%1/providers/%2/register", m_parent->m_auth_route, username_password_provider_key),
942✔
401
                   std::move(completion), {{"email", email}, {"password", password}});
942✔
402
}
942✔
403

404
void App::UsernamePasswordProviderClient::confirm_user(const std::string& token, const std::string& token_id,
405
                                                       UniqueFunction<void(Optional<AppError>)>&& completion)
406
{
2✔
407
    m_parent->log_debug("App: confirm_user");
2✔
408
    m_parent->post(util::format("%1/providers/%2/confirm", m_parent->m_auth_route, username_password_provider_key),
2✔
409
                   std::move(completion), {{"token", token}, {"tokenId", token_id}});
2✔
410
}
2✔
411

412
void App::UsernamePasswordProviderClient::resend_confirmation_email(
413
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
414
{
2✔
415
    m_parent->log_debug("App: resend_confirmation_email: %1", email);
2✔
416
    m_parent->post(
2✔
417
        util::format("%1/providers/%2/confirm/send", m_parent->m_auth_route, username_password_provider_key),
2✔
418
        std::move(completion), {{"email", email}});
2✔
419
}
2✔
420

421
void App::UsernamePasswordProviderClient::retry_custom_confirmation(
422
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
423
{
4✔
424
    m_parent->log_debug("App: retry_custom_confirmation: %1", email);
4✔
425
    m_parent->post(
4✔
426
        util::format("%1/providers/%2/confirm/call", m_parent->m_auth_route, username_password_provider_key),
4✔
427
        std::move(completion), {{"email", email}});
4✔
428
}
4✔
429

430
void App::UsernamePasswordProviderClient::send_reset_password_email(
431
    const std::string& email, UniqueFunction<void(Optional<AppError>)>&& completion)
432
{
×
433
    m_parent->log_debug("App: send_reset_password_email: %1", email);
×
434
    m_parent->post(util::format("%1/providers/%2/reset/send", m_parent->m_auth_route, username_password_provider_key),
×
435
                   std::move(completion), {{"email", email}});
×
436
}
×
437

438
void App::UsernamePasswordProviderClient::reset_password(const std::string& password, const std::string& token,
439
                                                         const std::string& token_id,
440
                                                         UniqueFunction<void(Optional<AppError>)>&& completion)
441
{
2✔
442
    m_parent->log_debug("App: reset_password");
2✔
443
    m_parent->post(util::format("%1/providers/%2/reset", m_parent->m_auth_route, username_password_provider_key),
2✔
444
                   std::move(completion), {{"password", password}, {"token", token}, {"tokenId", token_id}});
2✔
445
}
2✔
446

447
void App::UsernamePasswordProviderClient::call_reset_password_function(
448
    const std::string& email, const std::string& password, const BsonArray& args,
449
    UniqueFunction<void(Optional<AppError>)>&& completion)
450
{
6✔
451
    m_parent->log_debug("App: call_reset_password_function: %1", email);
6✔
452
    m_parent->post(util::format("%1/providers/%2/reset/call", m_parent->m_auth_route, username_password_provider_key),
6✔
453
                   std::move(completion), {{"email", email}, {"password", password}, {"arguments", args}});
6✔
454
}
6✔
455

456
// MARK: - UserAPIKeyProviderClient
457

458
std::string App::UserAPIKeyProviderClient::url_for_path(const std::string& path = "") const
459
{
82✔
460
    if (!path.empty()) {
82✔
461
        return m_auth_request_client.url_for_path(
58✔
462
            util::format("%1/%2/%3", auth_path, user_api_key_provider_key_path, path));
58✔
463
    }
58✔
464

12✔
465
    return m_auth_request_client.url_for_path(util::format("%1/%2", auth_path, user_api_key_provider_key_path));
24✔
466
}
24✔
467

468
void App::UserAPIKeyProviderClient::create_api_key(
469
    const std::string& name, const std::shared_ptr<SyncUser>& user,
470
    UniqueFunction<void(UserAPIKey&&, Optional<AppError>)>&& completion)
471
{
10✔
472
    Request req;
10✔
473
    req.method = HttpMethod::post;
10✔
474
    req.url = url_for_path();
10✔
475
    req.body = Bson(BsonDocument{{"name", name}}).to_string();
10✔
476
    req.uses_refresh_token = true;
10✔
477
    m_auth_request_client.do_authenticated_request(std::move(req), user,
10✔
478
                                                   UserAPIKeyResponseHandler{std::move(completion)});
10✔
479
}
10✔
480

481
void App::UserAPIKeyProviderClient::fetch_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
482
                                                  UniqueFunction<void(UserAPIKey&&, Optional<AppError>)>&& completion)
483
{
34✔
484
    Request req;
34✔
485
    req.method = HttpMethod::get;
34✔
486
    req.url = url_for_path(id.to_string());
34✔
487
    req.uses_refresh_token = true;
34✔
488
    m_auth_request_client.do_authenticated_request(std::move(req), user,
34✔
489
                                                   UserAPIKeyResponseHandler{std::move(completion)});
34✔
490
}
34✔
491

492
void App::UserAPIKeyProviderClient::fetch_api_keys(
493
    const std::shared_ptr<SyncUser>& user,
494
    UniqueFunction<void(std::vector<UserAPIKey>&&, Optional<AppError>)>&& completion)
495
{
14✔
496
    Request req;
14✔
497
    req.method = HttpMethod::get;
14✔
498
    req.url = url_for_path();
14✔
499
    req.uses_refresh_token = true;
14✔
500

7✔
501
    m_auth_request_client.do_authenticated_request(
14✔
502
        std::move(req), user, [completion = std::move(completion)](const Response& response) {
14✔
503
            if (auto error = AppUtils::check_for_errors(response)) {
14✔
504
                return completion({}, std::move(error));
4✔
505
            }
4✔
506

5✔
507
            try {
10✔
508
                auto json = parse<BsonArray>(response.body);
10✔
509
                std::vector<UserAPIKey> keys;
10✔
510
                keys.reserve(json.size());
10✔
511
                for (auto&& api_key_json : json) {
10✔
512
                    keys.push_back(UserAPIKeyResponseHandler::read_user_api_key(as<BsonDocument>(api_key_json)));
10✔
513
                }
10✔
514
                return completion(std::move(keys), {});
10✔
515
            }
10✔
516
            catch (AppError& e) {
×
517
                completion({}, std::move(e));
×
518
            }
×
519
        });
10✔
520
}
14✔
521

522

523
void App::UserAPIKeyProviderClient::delete_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
524
                                                   UniqueFunction<void(Optional<AppError>)>&& completion)
525
{
8✔
526
    Request req;
8✔
527
    req.method = HttpMethod::del;
8✔
528
    req.url = url_for_path(id.to_string());
8✔
529
    req.uses_refresh_token = true;
8✔
530
    m_auth_request_client.do_authenticated_request(std::move(req), user,
8✔
531
                                                   handle_default_response(std::move(completion)));
8✔
532
}
8✔
533

534
void App::UserAPIKeyProviderClient::enable_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
535
                                                   UniqueFunction<void(Optional<AppError>)>&& completion)
536
{
8✔
537
    Request req;
8✔
538
    req.method = HttpMethod::put;
8✔
539
    req.url = url_for_path(util::format("%1/enable", id.to_string()));
8✔
540
    req.uses_refresh_token = true;
8✔
541
    m_auth_request_client.do_authenticated_request(std::move(req), user,
8✔
542
                                                   handle_default_response(std::move(completion)));
8✔
543
}
8✔
544

545
void App::UserAPIKeyProviderClient::disable_api_key(const realm::ObjectId& id, const std::shared_ptr<SyncUser>& user,
546
                                                    UniqueFunction<void(Optional<AppError>)>&& completion)
547
{
8✔
548
    Request req;
8✔
549
    req.method = HttpMethod::put;
8✔
550
    req.url = url_for_path(util::format("%1/disable", id.to_string()));
8✔
551
    req.uses_refresh_token = true;
8✔
552
    m_auth_request_client.do_authenticated_request(std::move(req), user,
8✔
553
                                                   handle_default_response(std::move(completion)));
8✔
554
}
8✔
555
// MARK: - App
556

557
std::shared_ptr<SyncUser> App::current_user() const
558
{
875✔
559
    return m_sync_manager->get_current_user();
875✔
560
}
875✔
561

562
std::vector<std::shared_ptr<SyncUser>> App::all_users() const
563
{
38✔
564
    return m_sync_manager->all_users();
38✔
565
}
38✔
566

567
void App::get_profile(const std::shared_ptr<SyncUser>& sync_user,
568
                      UniqueFunction<void(const std::shared_ptr<SyncUser>&, Optional<AppError>)>&& completion)
569
{
998✔
570
    Request req;
998✔
571
    req.method = HttpMethod::get;
998✔
572
    req.timeout_ms = m_request_timeout_ms;
998✔
573
    req.uses_refresh_token = false;
998✔
574
    {
998✔
575
        std::lock_guard<std::mutex> lock(*m_route_mutex);
998✔
576
        req.url = util::format("%1/auth/profile", m_base_route);
998✔
577
    }
998✔
578

488✔
579
    do_authenticated_request(
998✔
580
        std::move(req), sync_user,
998✔
581
        [completion = std::move(completion), self = shared_from_this(), sync_user](const Response& profile_response) {
998✔
582
            if (auto error = AppUtils::check_for_errors(profile_response)) {
998✔
583
                return completion(nullptr, std::move(error));
×
584
            }
×
585

488✔
586
            try {
998✔
587
                auto profile_json = parse<BsonDocument>(profile_response.body);
998✔
588
                auto identities_json = get<BsonArray>(profile_json, "identities");
998✔
589

488✔
590
                std::vector<SyncUserIdentity> identities;
998✔
591
                identities.reserve(profile_json.size());
998✔
592
                for (auto& identity_json : identities_json) {
1,036✔
593
                    auto doc = as<BsonDocument>(identity_json);
1,036✔
594
                    identities.push_back(
1,036✔
595
                        SyncUserIdentity(get<std::string>(doc, "id"), get<std::string>(doc, "provider_type")));
1,036✔
596
                }
1,036✔
597

488✔
598
                sync_user->update_user_profile(std::move(identities),
998✔
599
                                               SyncUserProfile(get<BsonDocument>(profile_json, "data")));
998✔
600
                self->m_sync_manager->set_current_user(sync_user->identity());
998✔
601
                self->emit_change_to_subscribers(*self);
998✔
602
            }
998✔
603
            catch (const AppError& err) {
488✔
604
                return completion(nullptr, err);
×
605
            }
×
606

488✔
607
            return completion(sync_user, {});
998✔
608
        });
998✔
609
}
998✔
610

611
void App::attach_auth_options(BsonDocument& body)
612
{
1,022✔
613
    BsonDocument options;
1,022✔
614

500✔
615
    log_debug("App: version info: platform: %1  version: %2 - sdk: %3 - sdk version: %4 - core version: %5",
1,022✔
616
              m_config.device_info.platform, m_config.device_info.platform_version, m_config.device_info.sdk,
1,022✔
617
              m_config.device_info.sdk_version, m_config.device_info.core_version);
1,022✔
618
    options["appId"] = m_config.app_id;
1,022✔
619
    options["platform"] = m_config.device_info.platform;
1,022✔
620
    options["platformVersion"] = m_config.device_info.platform_version;
1,022✔
621
    options["sdk"] = m_config.device_info.sdk;
1,022✔
622
    options["sdkVersion"] = m_config.device_info.sdk_version;
1,022✔
623
    options["cpuArch"] = m_config.device_info.cpu_arch;
1,022✔
624
    options["deviceName"] = m_config.device_info.device_name;
1,022✔
625
    options["deviceVersion"] = m_config.device_info.device_version;
1,022✔
626
    options["frameworkName"] = m_config.device_info.framework_name;
1,022✔
627
    options["frameworkVersion"] = m_config.device_info.framework_version;
1,022✔
628
    options["coreVersion"] = m_config.device_info.core_version;
1,022✔
629
    options["bundleId"] = m_config.device_info.bundle_id;
1,022✔
630

500✔
631
    body["options"] = BsonDocument({{"device", options}});
1,022✔
632
}
1,022✔
633

634
void App::log_in_with_credentials(
635
    const AppCredentials& credentials, const std::shared_ptr<SyncUser>& linking_user,
636
    UniqueFunction<void(const std::shared_ptr<SyncUser>&, Optional<AppError>)>&& completion)
637
{
1,030✔
638
    if (would_log(util::Logger::Level::debug)) {
1,030✔
639
        auto app_info = util::format("app_id: %1", m_config.app_id);
1,028✔
640
        log_debug("App: log_in_with_credentials: %1", app_info);
1,028✔
641
    }
1,028✔
642
    // if we try logging in with an anonymous user while there
504✔
643
    // is already an anonymous session active, reuse it
504✔
644
    if (credentials.provider() == AuthProvider::ANONYMOUS) {
1,030✔
645
        for (auto&& user : m_sync_manager->all_users()) {
62✔
646
            if (user->is_anonymous()) {
40✔
647
                completion(switch_user(user), util::none);
8✔
648
                return;
8✔
649
            }
8✔
650
        }
40✔
651
    }
84✔
652

504✔
653
    // construct the route
504✔
654
    std::string route = util::format("%1/providers/%2/login%3", m_auth_route, credentials.provider_as_string(),
1,026✔
655
                                     linking_user ? "?link=true" : "");
1,016✔
656

500✔
657
    BsonDocument body = credentials.serialize_as_bson();
1,022✔
658
    attach_auth_options(body);
1,022✔
659

500✔
660
    // To ensure the location metadata is always kept up to date, requery the location info before
500✔
661
    // logging in the user. Since some SDK network transports (e.g. for JS) automatically handle
500✔
662
    // redirects, the location is not updated when a redirect response from the server is received.
500✔
663
    // This is especially necessary when the deployment model is changed, where the redirect response
500✔
664
    // provides information about the new location of the server and a location info update is
500✔
665
    // triggered. If the App never receives a redirect response from the server (because it was
500✔
666
    // automatically handled) after the deployment model was changed and the user was logged out,
500✔
667
    // the HTTP and websocket URL values will never be updated with the new server location.
500✔
668
    do_request(
1,022✔
669
        {HttpMethod::post, route, m_request_timeout_ms,
1,022✔
670
         get_request_headers(linking_user, RequestTokenType::AccessToken), Bson(body).to_string()},
1,022✔
671
        [completion = std::move(completion), credentials, linking_user,
1,022✔
672
         self = shared_from_this()](const Response& response) mutable {
1,022✔
673
            if (auto error = AppUtils::check_for_errors(response)) {
1,022✔
674
                self->log_error("App: log_in_with_credentials failed: %1 message: %2", response.http_status_code,
20✔
675
                                error->what());
20✔
676
                return completion(nullptr, std::move(error));
20✔
677
            }
20✔
678

490✔
679
            std::shared_ptr<realm::SyncUser> sync_user = linking_user;
1,002✔
680
            try {
1,002✔
681
                auto json = parse<BsonDocument>(response.body);
1,002✔
682
                if (linking_user) {
1,002✔
683
                    linking_user->update_access_token(get<std::string>(json, "access_token"));
12✔
684
                }
12✔
685
                else {
990✔
686
                    sync_user = self->m_sync_manager->get_user(
990✔
687
                        get<std::string>(json, "user_id"), get<std::string>(json, "refresh_token"),
990✔
688
                        get<std::string>(json, "access_token"), get<std::string>(json, "device_id"));
990✔
689
                }
990✔
690
            }
1,002✔
691
            catch (const AppError& e) {
492✔
692
                return completion(nullptr, e);
4✔
693
            }
4✔
694

488✔
695
            self->get_profile(sync_user, std::move(completion));
998✔
696
        },
998✔
697
        false);
1,022✔
698
}
1,022✔
699

700
void App::log_in_with_credentials(
701
    const AppCredentials& credentials,
702
    util::UniqueFunction<void(const std::shared_ptr<SyncUser>&, Optional<AppError>)>&& completion)
703
{
1,018✔
704
    App::log_in_with_credentials(credentials, nullptr, std::move(completion));
1,018✔
705
}
1,018✔
706

707
void App::log_out(const std::shared_ptr<SyncUser>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
708
{
42✔
709
    if (!user || user->state() != SyncUser::State::LoggedIn) {
42✔
710
        return completion(util::none);
4✔
711
    }
4✔
712

19✔
713
    auto refresh_token = user->refresh_token();
38✔
714
    user->log_out();
38✔
715

19✔
716
    std::string route = util::format("%1/auth/session", m_base_route);
38✔
717

19✔
718
    Request req;
38✔
719
    req.method = HttpMethod::del;
38✔
720
    req.url = route;
38✔
721
    req.timeout_ms = m_request_timeout_ms;
38✔
722
    req.uses_refresh_token = true;
38✔
723
    req.headers = get_request_headers();
38✔
724
    req.headers.insert({"Authorization", util::format("Bearer %1", refresh_token)});
38✔
725
    {
38✔
726
        std::lock_guard<std::mutex> lock(*m_route_mutex);
38✔
727
        req.url = util::format("%1/auth/session", m_base_route);
38✔
728
    }
38✔
729

19✔
730
    do_request(std::move(req),
38✔
731
               [self = shared_from_this(), completion = std::move(completion)](const Response& response) {
38✔
732
                   auto error = AppUtils::check_for_errors(response);
38✔
733
                   if (!error) {
38✔
734
                       self->emit_change_to_subscribers(*self);
38✔
735
                   }
38✔
736
                   completion(error);
38✔
737
               });
38✔
738
}
38✔
739

740
void App::log_out(UniqueFunction<void(Optional<AppError>)>&& completion)
741
{
18✔
742
    log_debug("App: log_out()");
18✔
743
    log_out(current_user(), std::move(completion));
18✔
744
}
18✔
745

746
bool App::verify_user_present(const std::shared_ptr<SyncUser>& user) const
747
{
48✔
748
    auto users = m_sync_manager->all_users();
48✔
749
    return std::any_of(users.begin(), users.end(), [&](auto&& u) {
52✔
750
        return u == user;
52✔
751
    });
52✔
752
}
48✔
753

754
std::shared_ptr<SyncUser> App::switch_user(const std::shared_ptr<SyncUser>& user) const
755
{
16✔
756
    if (!user || user->state() != SyncUser::State::LoggedIn) {
16✔
757
        throw AppError(ErrorCodes::ClientUserNotLoggedIn, "User is no longer valid or is logged out");
2✔
758
    }
2✔
759
    if (!verify_user_present(user)) {
14✔
UNCOV
760
        throw AppError(ErrorCodes::ClientUserNotFound, "User does not exist");
×
761
    }
×
762

7✔
763
    m_sync_manager->set_current_user(user->identity());
14✔
764
    emit_change_to_subscribers(*this);
14✔
765
    return current_user();
14✔
766
}
14✔
767

768
void App::remove_user(const std::shared_ptr<SyncUser>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
769
{
18✔
770
    if (!user || user->state() == SyncUser::State::Removed) {
18✔
771
        return completion(AppError(ErrorCodes::ClientUserNotFound, "User has already been removed"));
4✔
772
    }
4✔
773
    if (!verify_user_present(user)) {
14✔
774
        return completion(AppError(ErrorCodes::ClientUserNotFound, "No user has been found"));
×
775
    }
×
776

7✔
777
    if (user->is_logged_in()) {
14✔
778
        log_out(user, [user, completion = std::move(completion),
12✔
779
                       self = shared_from_this()](const Optional<AppError>& error) {
12✔
780
            self->m_sync_manager->remove_user(user->identity());
12✔
781
            return completion(error);
12✔
782
        });
12✔
783
    }
12✔
784
    else {
2✔
785
        m_sync_manager->remove_user(user->identity());
2✔
786
        return completion({});
2✔
787
    }
2✔
788
}
14✔
789

790
void App::delete_user(const std::shared_ptr<SyncUser>& user, UniqueFunction<void(Optional<AppError>)>&& completion)
791
{
12✔
792
    if (!user) {
12✔
793
        return completion(AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found."));
×
794
    }
×
795
    if (user->state() != SyncUser::State::LoggedIn) {
12✔
796
        return completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "User must be logged in to be deleted."));
4✔
797
    }
4✔
798

4✔
799
    if (!verify_user_present(user)) {
8✔
800
        return completion(AppError(ErrorCodes::ClientUserNotFound, "No user has been found."));
×
801
    }
×
802

4✔
803
    Request req;
8✔
804
    req.method = HttpMethod::del;
8✔
805
    req.timeout_ms = m_request_timeout_ms;
8✔
806
    req.url = url_for_path("/auth/delete");
8✔
807
    do_authenticated_request(std::move(req), user,
8✔
808
                             [self = shared_from_this(), completion = std::move(completion),
8✔
809
                              identitiy = user->identity()](const Response& response) {
8✔
810
                                 auto error = AppUtils::check_for_errors(response);
8✔
811
                                 if (!error) {
8✔
812
                                     self->emit_change_to_subscribers(*self);
8✔
813
                                     self->m_sync_manager->delete_user(identitiy);
8✔
814
                                 }
8✔
815
                                 completion(error);
8✔
816
                             });
8✔
817
}
8✔
818

819
void App::link_user(const std::shared_ptr<SyncUser>& user, const AppCredentials& credentials,
820
                    UniqueFunction<void(const std::shared_ptr<SyncUser>&, Optional<AppError>)>&& completion)
821
{
14✔
822
    if (!user) {
14✔
823
        return completion(nullptr,
×
824
                          AppError(ErrorCodes::ClientUserNotFound, "The specified user could not be found."));
×
825
    }
×
826
    if (user->state() != SyncUser::State::LoggedIn) {
14✔
827
        return completion(nullptr,
2✔
828
                          AppError(ErrorCodes::ClientUserNotLoggedIn, "The specified user is not logged in."));
2✔
829
    }
2✔
830
    if (!verify_user_present(user)) {
12✔
831
        return completion(nullptr, AppError(ErrorCodes::ClientUserNotFound, "The specified user was not found."));
×
832
    }
×
833

6✔
834
    App::log_in_with_credentials(credentials, user, std::move(completion));
12✔
835
}
12✔
836

837
void App::refresh_custom_data(const std::shared_ptr<SyncUser>& user,
838
                              UniqueFunction<void(Optional<AppError>)>&& completion)
839
{
8✔
840
    refresh_access_token(user, false, std::move(completion));
8✔
841
}
8✔
842

843
void App::refresh_custom_data(const std::shared_ptr<SyncUser>& user, bool update_location,
844
                              UniqueFunction<void(Optional<AppError>)>&& completion)
845
{
52✔
846
    refresh_access_token(user, update_location, std::move(completion));
52✔
847
}
52✔
848

849
std::string App::url_for_path(const std::string& path = "") const
850
{
100✔
851
    std::lock_guard<std::mutex> lock(*m_route_mutex);
100✔
852
    return util::format("%1%2", m_base_route, path);
100✔
853
}
100✔
854

855
std::string App::get_app_route(const Optional<std::string>& hostname) const
856
{
651✔
857
    if (hostname) {
651✔
858
        return *hostname + base_path + app_path + "/" + m_config.app_id;
92✔
859
    }
92✔
860
    else {
559✔
861
        return m_app_route;
559✔
862
    }
559✔
863
}
651✔
864

865
// FIXME: This passes back the response to bubble up any potential errors, making this somewhat leaky
866
void App::init_app_metadata(UniqueFunction<void(const Optional<Response>&)>&& completion,
867
                            const Optional<std::string>& new_hostname)
868
{
651✔
869
    std::string route;
651✔
870

317✔
871
    {
651✔
872
        std::unique_lock<std::mutex> lock(*m_route_mutex);
651✔
873
        // Skip if the app_metadata/location data has already been initialized and a new hostname is not provided
317✔
874
        if (!new_hostname && m_location_updated) {
651✔
875
            // Release the lock before calling the completion function
876
            lock.unlock();
×
877
            return completion(util::none); // early return
×
878
        }
×
879
        else {
651✔
880
            route = util::format("%1/location", new_hostname ? get_app_route(new_hostname) : get_app_route());
605✔
881
        }
651✔
882
    }
651✔
883
    Request req;
651✔
884
    req.method = HttpMethod::get;
651✔
885
    req.url = route;
651✔
886
    req.timeout_ms = m_request_timeout_ms;
651✔
887

317✔
888
    log_debug("App: request location: %1", route);
651✔
889

317✔
890
    m_config.transport->send_request_to_server(req, [self = shared_from_this(),
651✔
891
                                                     completion = std::move(completion)](const Response& response) {
651✔
892
        // If the response contains an error, then pass it up
317✔
893
        if (response.http_status_code >= 300 || (response.http_status_code < 200 && response.http_status_code != 0)) {
651✔
894
            return completion(response); // early return
108✔
895
        }
108✔
896

263✔
897
        try {
543✔
898
            auto json = parse<BsonDocument>(response.body);
543✔
899
            auto hostname = get<std::string>(json, "hostname");
543✔
900
            auto ws_hostname = get<std::string>(json, "ws_hostname");
543✔
901
            auto deployment_model = get<std::string>(json, "deployment_model");
543✔
902
            auto location = get<std::string>(json, "location");
543✔
903
            if (self->m_sync_manager->perform_metadata_update([&](SyncMetadataManager& manager) {
543✔
904
                    manager.set_app_metadata(deployment_model, location, hostname, ws_hostname);
529✔
905
                })) {
529✔
906
                // Update the hostname and sync route using the new app metadata info
256✔
907
                self->update_hostname(self->m_sync_manager->app_metadata());
529✔
908
            }
529✔
909
            else {
14✔
910
                // No metadata in use, update the hostname and sync route directly
7✔
911
                self->update_hostname(hostname, ws_hostname);
14✔
912
            }
14✔
913
            {
543✔
914
                std::lock_guard<std::mutex> lock(*self->m_route_mutex);
543✔
915
                self->m_location_updated = true;
543✔
916
            }
543✔
917
        }
543✔
918
        catch (const AppError&) {
267✔
919
            // Pass the response back to completion
4✔
920
            return completion(response);
8✔
921
        }
8✔
922
        completion(util::none);
535✔
923
    });
535✔
924
}
651✔
925

926
void App::post(std::string&& route, UniqueFunction<void(Optional<AppError>)>&& completion, const BsonDocument& body)
927
{
958✔
928
    do_request(Request{HttpMethod::post, std::move(route), m_request_timeout_ms, get_request_headers(),
958✔
929
                       Bson(body).to_string()},
958✔
930
               handle_default_response(std::move(completion)));
958✔
931
}
958✔
932

933
void App::update_metadata_and_resend(Request&& request, UniqueFunction<void(const Response&)>&& completion,
934
                                     const Optional<std::string>& new_hostname)
935
{
651✔
936
    // if we do not have metadata yet, we need to initialize it and send the
317✔
937
    // request once that's complete; or if a new_hostname is provided, re-initialize
317✔
938
    // the metadata with the updated location info
317✔
939
    init_app_metadata(
651✔
940
        [completion = std::move(completion), request = std::move(request), base_url = m_base_url,
651✔
941
         self = shared_from_this()](const util::Optional<Response>& response) mutable {
651✔
942
            if (response) {
651✔
943
                return self->handle_possible_redirect_response(std::move(request), *response, std::move(completion));
116✔
944
            }
116✔
945

259✔
946
            // if this is the first time we have received app metadata, the
259✔
947
            // original request will not have the correct URL hostname for
259✔
948
            // non global deployments.
259✔
949
            auto app_metadata = self->m_sync_manager->app_metadata();
535✔
950
            if (app_metadata && request.url.rfind(base_url, 0) != std::string::npos &&
535✔
951
                app_metadata->hostname != base_url) {
531✔
952
                request.url.replace(0, base_url.size(), app_metadata->hostname);
60✔
953
            }
60✔
954
            // Retry the original request with the updated url
259✔
955
            self->m_config.transport->send_request_to_server(
535✔
956
                std::move(request), [self = std::move(self), completion = std::move(completion)](
535✔
957
                                        Request&& request, const Response& response) mutable {
534✔
958
                    self->handle_possible_redirect_response(std::move(request), response, std::move(completion));
533✔
959
                });
533✔
960
        },
535✔
961
        new_hostname);
651✔
962
}
651✔
963

964
void App::do_request(Request&& request, UniqueFunction<void(const Response&)>&& completion, bool update_location)
965
{
3,655✔
966
    // Make sure the timeout value is set to the configured request timeout value
1,711✔
967
    request.timeout_ms = m_request_timeout_ms;
3,655✔
968

1,711✔
969
    // Refresh the location metadata every time an app is created (or when requested) to ensure the http
1,711✔
970
    // and websocket URL information is up to date.
1,711✔
971
    {
3,655✔
972
        std::unique_lock<std::mutex> lock(*m_route_mutex);
3,655✔
973
        if (update_location) {
3,655✔
974
            // Force the location to be updated before sending the request.
3✔
975
            m_location_updated = false;
6✔
976
        }
6✔
977
        if (!m_location_updated) {
3,655✔
978
            lock.unlock();
559✔
979
            // Location metadata has not yet been received after the App was created, update the location
271✔
980
            // info and then send the request
271✔
981
            update_metadata_and_resend(std::move(request), std::move(completion));
559✔
982
            return; // early return
559✔
983
        }
559✔
984
    }
3,096✔
985

1,440✔
986
    // location info has already been received after App was created
1,440✔
987
    m_config.transport->send_request_to_server(
3,096✔
988
        std::move(request), [self = shared_from_this(), completion = std::move(completion)](
3,096✔
989
                                Request&& request, const Response& response) mutable {
3,096✔
990
            self->handle_possible_redirect_response(std::move(request), response, std::move(completion));
3,096✔
991
        });
3,096✔
992
}
3,096✔
993

994
void App::handle_possible_redirect_response(Request&& request, const Response& response,
995
                                            UniqueFunction<void(const Response&)>&& completion)
996
{
3,745✔
997
    using namespace realm::sync;
3,745✔
998
    // If the response contains a redirection, then process it
1,756✔
999
    auto status_code = HTTPStatus(response.http_status_code);
3,745✔
1000
    if (status_code == HTTPStatus::MovedPermanently || status_code == HTTPStatus::PermanentRedirect) {
3,745✔
1001
        handle_redirect_response(std::move(request), response, std::move(completion));
100✔
1002
    }
100✔
1003
    else {
3,645✔
1004
        completion(response);
3,645✔
1005
    }
3,645✔
1006
}
3,745✔
1007

1008
void App::handle_redirect_response(Request&& request, const Response& response,
1009
                                   UniqueFunction<void(const Response&)>&& completion)
1010
{
100✔
1011
    // Permanent redirect - get the location and init the metadata again
50✔
1012
    // Look for case insensitive redirect "location" in headers
50✔
1013
    auto location = AppUtils::find_header("location", response.headers);
100✔
1014
    if (!location || location->second.empty()) {
100✔
1015
        // Location not found in the response, pass error response up the chain
2✔
1016
        Response error;
4✔
1017
        error.http_status_code = response.http_status_code;
4✔
1018
        error.client_error_code = ErrorCodes::ClientRedirectError;
4✔
1019
        error.body = "Redirect response missing location header";
4✔
1020
        return completion(error); // early return
4✔
1021
    }
4✔
1022

48✔
1023
    // Make sure we don't do too many redirects (max_http_redirects (20) is an arbitrary number)
48✔
1024
    if (++request.redirect_count > max_http_redirects) {
96✔
1025
        Response error;
4✔
1026
        error.http_status_code = response.http_status_code;
4✔
1027
        error.custom_status_code = 0;
4✔
1028
        error.client_error_code = ErrorCodes::ClientTooManyRedirects;
4✔
1029
        error.body = util::format("number of redirections exceeded %1", max_http_redirects);
4✔
1030
        return completion(error); // early return
4✔
1031
    }
4✔
1032

46✔
1033
    // Update the metadata from the new location after trimming the url (limit to `scheme://host[:port]`)
46✔
1034
    std::string_view new_url = location->second;
92✔
1035
    // Find the end of the scheme/protocol part (e.g. 'https://', 'http://')
46✔
1036
    auto scheme_end = new_url.find("://");
92✔
1037
    scheme_end = scheme_end != std::string_view::npos ? scheme_end + std::char_traits<char>::length("://") : 0;
92✔
1038
    // Trim off any trailing path/anchor/query string after the host/port
46✔
1039
    if (auto split = new_url.find_first_of("/#?", scheme_end); split != std::string_view::npos) {
92✔
1040
        new_url.remove_suffix(new_url.size() - split);
×
1041
    }
×
1042

46✔
1043
    update_metadata_and_resend(std::move(request), std::move(completion), std::string(new_url));
92✔
1044
}
92✔
1045

1046
void App::do_authenticated_request(Request&& request, const std::shared_ptr<SyncUser>& sync_user,
1047
                                   util::UniqueFunction<void(const Response&)>&& completion)
1048
{
1,565✔
1049
    request.headers = get_request_headers(sync_user, request.uses_refresh_token ? RequestTokenType::RefreshToken
729✔
1050
                                                                                : RequestTokenType::AccessToken);
1,524✔
1051

688✔
1052
    log_debug("App: do_authenticated_request: %1 %2", httpmethod_to_string(request.method), request.url);
1,565✔
1053
    auto completion_2 = [completion = std::move(completion), request, sync_user,
1,565✔
1054
                         self = shared_from_this()](const Response& response) mutable {
1,564✔
1055
        if (auto error = AppUtils::check_for_errors(response)) {
1,563✔
1056
            self->handle_auth_failure(std::move(*error), std::move(response), std::move(request), sync_user,
68✔
1057
                                      std::move(completion));
68✔
1058
        }
68✔
1059
        else {
1,495✔
1060
            completion(std::move(response));
1,495✔
1061
        }
1,495✔
1062
    };
1,563✔
1063
    do_request(std::move(request), std::move(completion_2));
1,565✔
1064
}
1,565✔
1065

1066
void App::handle_auth_failure(const AppError& error, const Response& response, Request&& request,
1067
                              const std::shared_ptr<SyncUser>& sync_user,
1068
                              util::UniqueFunction<void(const Response&)>&& completion)
1069
{
68✔
1070
    // Only handle auth failures
34✔
1071
    if (*error.additional_status_code == 401) {
68✔
1072
        if (request.uses_refresh_token) {
28✔
1073
            if (sync_user && sync_user->is_logged_in()) {
18!
1074
                sync_user->log_out();
×
1075
            }
×
1076
            completion(std::move(response));
18✔
1077
            return;
18✔
1078
        }
18✔
1079
    }
40✔
1080
    else {
40✔
1081
        completion(std::move(response));
40✔
1082
        return;
40✔
1083
    }
40✔
1084

5✔
1085
    App::refresh_access_token(sync_user, false,
10✔
1086
                              [self = shared_from_this(), request = std::move(request),
10✔
1087
                               completion = std::move(completion), response = std::move(response),
10✔
1088
                               sync_user](Optional<AppError>&& error) mutable {
10✔
1089
                                  if (!error) {
10✔
1090
                                      // assign the new access_token to the auth header
3✔
1091
                                      request.headers = get_request_headers(sync_user, RequestTokenType::AccessToken);
6✔
1092
                                      self->do_request(std::move(request), std::move(completion));
6✔
1093
                                  }
6✔
1094
                                  else {
4✔
1095
                                      // pass the error back up the chain
2✔
1096
                                      completion(std::move(response));
4✔
1097
                                  }
4✔
1098
                              });
10✔
1099
}
10✔
1100

1101
/// MARK: - refresh access token
1102
void App::refresh_access_token(const std::shared_ptr<SyncUser>& sync_user, bool update_location,
1103
                               util::UniqueFunction<void(Optional<AppError>)>&& completion)
1104
{
70✔
1105
    if (!sync_user) {
70✔
1106
        completion(AppError(ErrorCodes::ClientUserNotFound, "No current user exists"));
2✔
1107
        return;
2✔
1108
    }
2✔
1109

34✔
1110
    if (!sync_user->is_logged_in()) {
68✔
1111
        completion(AppError(ErrorCodes::ClientUserNotLoggedIn, "The user is not logged in"));
2✔
1112
        return;
2✔
1113
    }
2✔
1114

33✔
1115
    std::string route;
66✔
1116
    {
66✔
1117
        std::lock_guard<std::mutex> lock(*m_route_mutex);
66✔
1118
        route = util::format("%1/auth/session", m_base_route);
66✔
1119
    }
66✔
1120

33✔
1121
    log_debug("App: refresh_access_token: email: %1 %2", sync_user->user_profile().email(),
66✔
1122
              update_location ? "(updating location)" : "");
63✔
1123

33✔
1124
    // If update_location is set, force the location info to be updated before sending the request
33✔
1125
    do_request(
66✔
1126
        {HttpMethod::post, std::move(route), m_request_timeout_ms,
66✔
1127
         get_request_headers(sync_user, RequestTokenType::RefreshToken)},
66✔
1128
        [completion = std::move(completion), sync_user](const Response& response) {
66✔
1129
            if (auto error = AppUtils::check_for_errors(response)) {
66✔
1130
                return completion(std::move(error));
32✔
1131
            }
32✔
1132

17✔
1133
            try {
34✔
1134
                auto json = parse<BsonDocument>(response.body);
34✔
1135
                sync_user->update_access_token(get<std::string>(json, "access_token"));
34✔
1136
            }
34✔
1137
            catch (AppError& err) {
19✔
1138
                return completion(std::move(err));
4✔
1139
            }
4✔
1140

15✔
1141
            return completion(util::none);
30✔
1142
        },
30✔
1143
        update_location);
66✔
1144
}
66✔
1145

1146
std::string App::function_call_url_path() const
1147
{
475✔
1148
    std::lock_guard<std::mutex> lock(*m_route_mutex);
475✔
1149
    return util::format("%1/app/%2/functions/call", m_base_route, m_config.app_id);
475✔
1150
}
475✔
1151

1152
void App::call_function(const std::shared_ptr<SyncUser>& user, const std::string& name, std::string_view args_ejson,
1153
                        const Optional<std::string>& service_name_opt,
1154
                        UniqueFunction<void(const std::string*, Optional<AppError>)>&& completion)
1155
{
467✔
1156
    auto service_name = service_name_opt ? *service_name_opt : "<none>";
462✔
1157
    if (would_log(util::Logger::Level::debug)) {
467✔
1158
        log_debug("App: call_function: %1 service_name: %2 args_bson: %3", name, service_name, args_ejson);
467✔
1159
    }
467✔
1160

150✔
1161
    auto args = util::format("{\"arguments\":%1,\"name\":%2%3}", args_ejson, nlohmann::json(name).dump(),
467✔
1162
                             service_name_opt ? (",\"service\":" + nlohmann::json(service_name).dump()) : "");
462✔
1163

150✔
1164
    do_authenticated_request(
467✔
1165
        Request{HttpMethod::post, function_call_url_path(), m_request_timeout_ms, {}, std::move(args), false}, user,
467✔
1166
        [self = shared_from_this(), name = name, service_name = std::move(service_name),
467✔
1167
         completion = std::move(completion)](const Response& response) {
466✔
1168
            if (auto error = AppUtils::check_for_errors(response)) {
465✔
1169
                self->log_error("App: call_function: %1 service_name: %2 -> %3 ERROR: %4", name, service_name,
18✔
1170
                                response.http_status_code, error->what());
18✔
1171
                return completion(nullptr, error);
18✔
1172
            }
18✔
1173
            completion(&response.body, util::none);
447✔
1174
        });
447✔
1175
}
467✔
1176

1177
void App::call_function(const std::shared_ptr<SyncUser>& user, const std::string& name, const BsonArray& args_bson,
1178
                        const Optional<std::string>& service_name,
1179
                        UniqueFunction<void(Optional<Bson>&&, Optional<AppError>)>&& completion)
1180
{
467✔
1181
    auto service_name2 = service_name ? *service_name : "<none>";
462✔
1182
    std::stringstream args_ejson;
467✔
1183
    args_ejson << "[";
467✔
1184
    for (auto&& arg : args_bson) {
481✔
1185
        if (&arg != &args_bson.front())
481✔
1186
            args_ejson << ',';
16✔
1187
        args_ejson << arg.toJson();
481✔
1188
    }
481✔
1189
    args_ejson << "]";
467✔
1190

150✔
1191
    call_function(user, name, std::move(args_ejson).str(), service_name,
467✔
1192
                  [self = shared_from_this(), name, service_name = std::move(service_name2),
467✔
1193
                   completion = std::move(completion)](const std::string* response, util::Optional<AppError>&& err) {
466✔
1194
                      if (err) {
465✔
1195
                          return completion({}, err);
18✔
1196
                      }
18✔
1197
                      util::Optional<Bson> body_as_bson;
447✔
1198
                      try {
447✔
1199
                          body_as_bson = bson::parse(*response);
447✔
1200
                          if (self->would_log(util::Logger::Level::debug)) {
447✔
1201
                              self->log_debug("App: call_function: %1 service_name: %2 - results: %3", name,
447✔
1202
                                              service_name, body_as_bson ? body_as_bson->to_string() : "<none>");
447✔
1203
                          }
447✔
1204
                      }
447✔
1205
                      catch (const std::exception& e) {
140✔
1206
                          self->log_error("App: call_function: %1 service_name: %2 - error parsing result: %3", name,
×
1207
                                          service_name, e.what());
×
1208
                          return completion(util::none, AppError(ErrorCodes::BadBsonParse, e.what()));
×
1209
                      };
447✔
1210
                      completion(std::move(body_as_bson), util::none);
447✔
1211
                  });
447✔
1212
}
467✔
1213

1214
void App::call_function(const std::shared_ptr<SyncUser>& user, const std::string& name, const BsonArray& args_bson,
1215
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1216
{
6✔
1217
    call_function(user, name, args_bson, util::none, std::move(completion));
6✔
1218
}
6✔
1219

1220
void App::call_function(const std::string& name, const BsonArray& args_bson,
1221
                        const Optional<std::string>& service_name,
1222
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1223
{
×
1224
    call_function(m_sync_manager->get_current_user(), name, args_bson, service_name, std::move(completion));
×
1225
}
×
1226

1227
void App::call_function(const std::string& name, const BsonArray& args_bson,
1228
                        UniqueFunction<void(Optional<bson::Bson>&&, Optional<AppError>)>&& completion)
1229
{
4✔
1230
    call_function(m_sync_manager->get_current_user(), name, args_bson, std::move(completion));
4✔
1231
}
4✔
1232

1233
Request App::make_streaming_request(const std::shared_ptr<SyncUser>& user, const std::string& name,
1234
                                    const BsonArray& args_bson, const Optional<std::string>& service_name) const
1235
{
8✔
1236
    auto args = BsonDocument{
8✔
1237
        {"arguments", args_bson},
8✔
1238
        {"name", name},
8✔
1239
    };
8✔
1240
    if (service_name) {
8✔
1241
        args["service"] = *service_name;
8✔
1242
    }
8✔
1243
    const auto args_json = Bson(args).to_string();
8✔
1244

4✔
1245
    auto args_base64 = std::string(util::base64_encoded_size(args_json.size()), '\0');
8✔
1246
    util::base64_encode(args_json.data(), args_json.size(), args_base64.data(), args_base64.size());
8✔
1247

4✔
1248
    auto url = function_call_url_path() + "?baas_request=" + util::uri_percent_encode(args_base64);
8✔
1249
    if (user) {
8✔
1250
        url += "&baas_at=";
2✔
1251
        url += user->access_token(); // doesn't need url encoding
2✔
1252
    }
2✔
1253

4✔
1254
    return Request{
8✔
1255
        HttpMethod::get,
8✔
1256
        url,
8✔
1257
        m_request_timeout_ms,
8✔
1258
        {{"Accept", "text/event-stream"}},
8✔
1259
    };
8✔
1260
}
8✔
1261

1262
PushClient App::push_notification_client(const std::string& service_name)
1263
{
10✔
1264
    return PushClient(service_name, m_config.app_id, m_request_timeout_ms, shared_from_this());
10✔
1265
}
10✔
1266

1267
} // namespace app
1268
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc