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

realm / realm-core / 2210

09 Apr 2024 03:41PM UTC coverage: 92.601% (+0.5%) from 92.106%
2210

push

Evergreen

web-flow
Merge pull request #7300 from realm/tg/rework-metadata-storage

Rework sync user handling and metadata storage

102800 of 195548 branches covered (52.57%)

3051 of 3153 new or added lines in 46 files covered. (96.76%)

41 existing lines in 11 files now uncovered.

249129 of 269035 relevant lines covered (92.6%)

46864217.27 hits per line

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

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

19
#include <realm/object-store/sync/app_user.hpp>
20

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

29
namespace realm::app {
30

31
UserIdentity::UserIdentity(const std::string& id, const std::string& provider_type)
32
    : id(id)
33
    , provider_type(provider_type)
34
{
2,899✔
35
}
2,899✔
36

37
User::User(Private, std::shared_ptr<app::App> app, std::string_view user_id)
38
    : m_app(std::move(app))
39
    , m_app_id(m_app->app_id())
40
    , m_user_id(user_id)
41
{
4,621✔
42
    REALM_ASSERT(m_app);
4,621✔
43
    m_app->register_sync_user(*this);
4,621✔
44
}
4,621✔
45

46
User::~User()
47
{
4,621✔
48
    if (m_app) {
4,621✔
49
        m_app->unregister_sync_user(*this);
4,599✔
50
    }
4,599✔
51
}
4,621✔
52

53
std::string User::user_id() const noexcept
54
{
21,990✔
55
    return m_user_id;
21,990✔
56
}
21,990✔
57

58
std::string User::app_id() const noexcept
59
{
22✔
60
    return m_app_id;
22✔
61
}
22✔
62

63
std::vector<std::string> User::legacy_identities() const
64
{
14✔
65
    util::CheckedLockGuard lock(m_mutex);
14✔
66
    return m_data.legacy_identities;
14✔
67
}
14✔
68

69
std::string User::access_token() const
70
{
3,576✔
71
    util::CheckedLockGuard lock(m_mutex);
3,576✔
72
    return m_data.access_token.token;
3,576✔
73
}
3,576✔
74

75
std::string User::refresh_token() const
76
{
102✔
77
    util::CheckedLockGuard lock(m_mutex);
102✔
78
    return m_data.refresh_token.token;
102✔
79
}
102✔
80

81
SyncUser::State User::state() const
82
{
8,053✔
83
    util::CheckedLockGuard lock(m_mutex);
8,053✔
84
    if (!m_app)
8,053✔
85
        return SyncUser::State::Removed;
34✔
86
    return m_data.access_token ? SyncUser::State::LoggedIn : SyncUser::State::LoggedOut;
8,019✔
87
}
8,019✔
88

89
bool User::is_anonymous() const
90
{
33✔
91
    util::CheckedLockGuard lock(m_mutex);
33✔
92
    return do_is_anonymous();
33✔
93
}
33✔
94

95
bool User::do_is_anonymous() const
96
{
33✔
97
    return m_data.access_token && m_data.identities.size() == 1 &&
33✔
98
           m_data.identities[0].provider_type == app::IdentityProviderAnonymous;
21✔
99
}
33✔
100

101
std::string User::device_id() const
102
{
1✔
103
    util::CheckedLockGuard lock(m_mutex);
1✔
104
    return m_data.device_id;
1✔
105
}
1✔
106

107
bool User::has_device_id() const
108
{
1✔
109
    // The server will sometimes send us an all-zero device ID as a way to
110
    // explicitly signal that it did not generate a device ID for this login.
111
    util::CheckedLockGuard lock(m_mutex);
1✔
112
    return !m_data.device_id.empty() && m_data.device_id != "000000000000000000000000";
1✔
113
}
1✔
114

115
UserProfile User::user_profile() const
116
{
52✔
117
    util::CheckedLockGuard lock(m_mutex);
52✔
118
    return m_data.profile;
52✔
119
}
52✔
120

121
std::vector<UserIdentity> User::identities() const
122
{
15✔
123
    util::CheckedLockGuard lock(m_mutex);
15✔
124
    return m_data.identities;
15✔
125
}
15✔
126

127
std::optional<bson::BsonDocument> User::custom_data() const
128
{
2✔
129
    util::CheckedLockGuard lock(m_mutex);
2✔
130
    return m_data.access_token.user_data;
2✔
131
}
2✔
132

133
std::shared_ptr<App> User::app() const
134
{
100✔
135
    util::CheckedLockGuard lock(m_mutex);
100✔
136
    return m_app;
100✔
137
}
100✔
138

139
SyncManager* User::sync_manager()
140
{
1,044✔
141
    util::CheckedLockGuard lock(m_mutex);
1,044✔
142
    return m_app ? m_app->sync_manager().get() : nullptr;
1,041✔
143
}
1,044✔
144

145
app::MongoClient User::mongo_client(const std::string& service_name)
146
{
42✔
147
    util::CheckedLockGuard lock(m_mutex);
42✔
148
    return app::MongoClient(shared_from_this(), m_app->app_service_client(), service_name);
42✔
149
}
42✔
150

151
bool User::access_token_refresh_required() const
152
{
805✔
153
    using namespace std::chrono;
805✔
154
    constexpr size_t buffer_seconds = 5; // arbitrary
805✔
155
    util::CheckedLockGuard lock(m_mutex);
805✔
156
    const auto now = duration_cast<seconds>(system_clock::now().time_since_epoch()).count() +
805✔
157
                     m_seconds_to_adjust_time_for_testing.load(std::memory_order_relaxed);
805✔
158
    const auto threshold = now - buffer_seconds;
805✔
159
    return m_data.access_token && m_data.access_token.expires_at < static_cast<int64_t>(threshold);
805✔
160
}
805✔
161

162
void User::log_out()
163
{
4✔
164
    if (auto app = this->app()) {
4✔
165
        app->log_out(shared_from_this(), nullptr);
4✔
166
    }
4✔
167
}
4✔
168

169
void User::detach_and_tear_down()
170
{
30✔
171
    std::shared_ptr<App> app;
30✔
172
    {
30✔
173
        util::CheckedLockGuard lk(m_mutex);
30✔
174
        m_data.access_token.token.clear();
30✔
175
        m_data.refresh_token.token.clear();
30✔
176
        app = std::exchange(m_app, nullptr);
30✔
177
    }
30✔
178

179
    if (app) {
30✔
180
        app->sync_manager()->update_sessions_for(*this, SyncUser::State::LoggedIn, SyncUser::State::Removed, {});
22✔
181
        app->unregister_sync_user(*this);
22✔
182
    }
22✔
183
}
30✔
184

185
void User::update_data_for_testing(util::FunctionRef<void(UserData&)> fn)
186
{
10✔
187
    UserData data;
10✔
188
    {
10✔
189
        util::CheckedLockGuard lock(m_mutex);
10✔
190
        data = m_data;
10✔
191
    }
10✔
192
    fn(data);
10✔
193
    update_backing_data(std::move(data));
10✔
194
}
10✔
195

196
void User::update_backing_data(std::optional<UserData>&& data)
197
{
7,076✔
198
    if (!data) {
7,076✔
199
        detach_and_tear_down();
26✔
200
        emit_change_to_subscribers(*this);
26✔
201
        return;
26✔
202
    }
26✔
203

204
    std::string new_token;
7,050✔
205
    SyncUser::State old_state;
7,050✔
206
    SyncUser::State new_state = data->access_token ? SyncUser::State::LoggedIn : SyncUser::State::LoggedOut;
7,035✔
207
    std::shared_ptr<SyncManager> sync_manager;
7,050✔
208
    {
7,050✔
209
        util::CheckedLockGuard lock(m_mutex);
7,050✔
210
        if (!m_app) {
7,050✔
NEW
211
            return; // is already detached
×
NEW
212
        }
×
213
        sync_manager = m_app->sync_manager();
7,050✔
214
        old_state = m_data.access_token ? SyncUser::State::LoggedIn : SyncUser::State::LoggedOut;
4,625✔
215
        if (new_state == SyncUser::State::LoggedIn && data->access_token != m_data.access_token)
7,050✔
216
            new_token = data->access_token.token;
4,648✔
217
        m_data = std::move(*data);
7,050✔
218
    }
7,050✔
219

220
    sync_manager->update_sessions_for(*this, old_state, new_state, new_token);
7,050✔
221
    emit_change_to_subscribers(*this);
7,050✔
222
}
7,050✔
223

224
void User::request_log_out()
225
{
7✔
226
    if (auto app = this->app()) {
7✔
227
        auto new_state = is_anonymous() ? SyncUser::State::Removed : SyncUser::State::LoggedOut;
4✔
228
        app->m_metadata_store->log_out(m_user_id, new_state);
7✔
229
        update_backing_data(app->m_metadata_store->get_user(m_user_id));
7✔
230
    }
7✔
231
}
7✔
232

233
void User::request_refresh_user(util::UniqueFunction<void(util::Optional<app::AppError>)>&& completion)
NEW
234
{
×
NEW
235
    if (auto app = this->app()) {
×
NEW
236
        app->get_profile(shared_from_this(), [completion = std::move(completion)](auto, auto error) {
×
NEW
237
            completion(std::move(error));
×
NEW
238
        });
×
NEW
239
    }
×
NEW
240
}
×
241

242
void User::request_refresh_location(util::UniqueFunction<void(util::Optional<app::AppError>)>&& completion)
243
{
17✔
244
    if (auto app = this->app()) {
17✔
245
        bool update_location = true;
17✔
246
        app->refresh_access_token(shared_from_this(), update_location, std::move(completion));
17✔
247
    }
17✔
248
}
17✔
249

250
void User::request_access_token(util::UniqueFunction<void(util::Optional<app::AppError>)>&& completion)
251
{
15✔
252
    if (auto app = this->app()) {
15✔
253
        bool update_location = false;
15✔
254
        app->refresh_access_token(shared_from_this(), update_location, std::move(completion));
15✔
255
    }
15✔
256
}
15✔
257

258
void User::track_realm(std::string_view path)
259
{
14✔
260
    if (auto app = this->app()) {
14✔
261
        app->m_metadata_store->add_realm_path(m_user_id, path);
14✔
262
    }
14✔
263
}
14✔
264

265
std::string User::create_file_action(SyncFileAction action, std::string_view original_path,
266
                                     std::optional<std::string> requested_recovery_dir)
267
{
37✔
268
    if (auto app = this->app()) {
37✔
269
        std::string recovery_path;
37✔
270
        if (action == SyncFileAction::BackUpThenDeleteRealm) {
37✔
271
            recovery_path =
37✔
272
                util::reserve_unique_file_name(app->m_file_manager->recovery_directory_path(requested_recovery_dir),
37✔
273
                                               util::create_timestamped_template("recovered_realm"));
37✔
274
        }
37✔
275
        app->m_metadata_store->create_file_action(action, original_path, recovery_path);
37✔
276
        return recovery_path;
37✔
277
    }
37✔
NEW
278
    return "";
×
NEW
279
}
×
280

281
void User::refresh_custom_data(util::UniqueFunction<void(util::Optional<app::AppError>)> completion_block)
282
    REQUIRES(!m_mutex)
283
{
6✔
284
    refresh_custom_data(false, std::move(completion_block));
6✔
285
}
6✔
286

287
void User::refresh_custom_data(bool update_location,
288
                               util::UniqueFunction<void(util::Optional<app::AppError>)> completion_block)
289
{
6✔
290
    if (auto app = this->app()) {
6✔
291
        app->refresh_custom_data(shared_from_this(), update_location, std::move(completion_block));
5✔
292
        return;
5✔
293
    }
5✔
294
    completion_block(app::AppError(
1✔
295
        ErrorCodes::ClientUserNotFound,
1✔
296
        util::format("Cannot initiate a refresh on user '%1' because the user has been removed", m_user_id)));
1✔
297
}
1✔
298

299
std::string User::path_for_realm(const SyncConfig& config, std::optional<std::string> custom_file_name) const
NEW
300
{
×
NEW
301
    if (auto app = this->app()) {
×
NEW
302
        return app->m_file_manager->path_for_realm(config, std::move(custom_file_name));
×
NEW
303
    }
×
NEW
304
    return "";
×
NEW
305
}
×
306
} // namespace realm::app
307

308
namespace std {
309
size_t hash<realm::app::UserIdentity>::operator()(const realm::app::UserIdentity& k) const
NEW
310
{
×
NEW
311
    return ((hash<string>()(k.id) ^ (hash<string>()(k.provider_type) << 1)) >> 1);
×
NEW
312
}
×
313
} // namespace std
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc