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

realm / realm-core / thomas.goyne_275

09 Apr 2024 03:33AM UTC coverage: 92.608% (+0.5%) from 92.088%
thomas.goyne_275

Pull #7300

Evergreen

tgoyne
Extract some duplicated code in PushClient
Pull Request #7300: Rework sync user handling and metadata storage

102672 of 194970 branches covered (52.66%)

3165 of 3247 new or added lines in 46 files covered. (97.47%)

34 existing lines in 9 files now uncovered.

249420 of 269329 relevant lines covered (92.61%)

45087511.34 hits per line

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

94.44
/src/realm/object-store/sync/app_user.hpp
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
#ifndef REALM_OS_APP_USER_HPP
20
#define REALM_OS_APP_USER_HPP
21

22
#include <realm/object-store/sync/sync_user.hpp>
23
#include <realm/object-store/sync/subscribable.hpp>
24
#include <realm/util/bson/bson.hpp>
25
#include <realm/util/checked_mutex.hpp>
26
#include <realm/util/function_ref.hpp>
27

28
#include <memory>
29
#include <string>
30
#include <vector>
31

32
namespace realm {
33
struct SyncConfig;
34
}
35

36
namespace realm::app {
37
class App;
38
struct AppError;
39
class MetadataStore;
40
class MongoClient;
41

42
struct UserProfile {
43
    // The full name of the user.
44
    std::optional<std::string> name() const
45
    {
8✔
46
        return get_field("name");
8✔
47
    }
8✔
48
    // The email address of the user.
49
    std::optional<std::string> email() const
50
    {
106✔
51
        return get_field("email");
106✔
52
    }
106✔
53
    // A URL to the user's profile picture.
54
    std::optional<std::string> picture_url() const
55
    {
8✔
56
        return get_field("picture_url");
8✔
57
    }
8✔
58
    // The first name of the user.
59
    std::optional<std::string> first_name() const
60
    {
8✔
61
        return get_field("first_name");
8✔
62
    }
8✔
63
    // The last name of the user.
64
    std::optional<std::string> last_name() const
65
    {
8✔
66
        return get_field("last_name");
8✔
67
    }
8✔
68
    // The gender of the user.
69
    std::optional<std::string> gender() const
70
    {
8✔
71
        return get_field("gender");
8✔
72
    }
8✔
73
    // The birthdate of the user.
74
    std::optional<std::string> birthday() const
75
    {
8✔
76
        return get_field("birthday");
8✔
77
    }
8✔
78
    // The minimum age of the user.
79
    std::optional<std::string> min_age() const
80
    {
8✔
81
        return get_field("min_age");
8✔
82
    }
8✔
83
    // The maximum age of the user.
84
    std::optional<std::string> max_age() const
85
    {
8✔
86
        return get_field("max_age");
8✔
87
    }
8✔
88

89
    bson::Bson operator[](const std::string& key) const
90
    {
2✔
91
        return m_data.at(key);
2✔
92
    }
2✔
93

94
    const bson::BsonDocument& data() const
95
    {
1,094✔
96
        return m_data;
1,094✔
97
    }
1,094✔
98

99
    UserProfile(bson::BsonDocument&& data)
100
        : m_data(std::move(data))
101
    {
5,680✔
102
    }
5,680✔
103
    UserProfile() = default;
15,969✔
104

105
private:
106
    bson::BsonDocument m_data;
107

108
    std::optional<std::string> get_field(const char* name) const
109
    {
170✔
110
        if (auto val = m_data.find(name)) {
170✔
111
            return static_cast<std::string>((*val));
96✔
112
        }
96✔
113
        return util::none;
74✔
114
    }
74✔
115
};
116

117
// A struct that represents an identity that a `User` is linked to
118
struct UserIdentity {
119
    // the id of the identity
120
    std::string id;
121
    // the associated provider type of the identity
122
    std::string provider_type;
123

124
    UserIdentity(const std::string& id, const std::string& provider_type);
125

126
    bool operator==(const UserIdentity& other) const
127
    {
40✔
128
        return id == other.id && provider_type == other.provider_type;
40✔
129
    }
40✔
130

131
    bool operator!=(const UserIdentity& other) const
NEW
132
    {
×
NEW
133
        return id != other.id || provider_type != other.provider_type;
×
NEW
134
    }
×
135
};
136

137
struct UserData {
138
    // Current refresh token or empty if user is logged out
139
    RealmJWT refresh_token;
140
    // Current access token or empty if user is logged out
141
    RealmJWT access_token;
142
    // UUIDs which used to be used to generate local Realm file paths. Now only
143
    // used to locate existing files.
144
    std::vector<std::string> legacy_identities;
145
    // Identities which were used to log into this user
146
    std::vector<UserIdentity> identities;
147
    // Id for the device which this user was logged in on. Users are not
148
    // portable between devices so this cannot be changed after the user
149
    // is created
150
    std::string device_id;
151
    // Server-stored user profile
152
    UserProfile profile;
153
};
154

155
class User final : public SyncUser, public std::enable_shared_from_this<User>, public Subscribable<User> {
156
    struct Private {};
157

158
public:
159
    // ------------------------------------------------------------------------
160
    // SyncUser implementation
161

162
    std::string user_id() const noexcept override;
163
    std::string app_id() const noexcept override;
164
    std::vector<std::string> legacy_identities() const override REQUIRES(!m_mutex);
165

166
    std::string access_token() const override REQUIRES(!m_mutex);
167
    std::string refresh_token() const override REQUIRES(!m_mutex);
168
    SyncUser::State state() const override REQUIRES(!m_mutex);
169

170
    /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns
171
    /// true.
172
    bool access_token_refresh_required() const override REQUIRES(!m_mutex);
173

174
    SyncManager* sync_manager() override REQUIRES(!m_mutex);
175
    void request_log_out() override REQUIRES(!m_mutex);
176
    void request_refresh_user(util::UniqueFunction<void(std::optional<app::AppError>)>&&) override REQUIRES(!m_mutex);
177
    void request_refresh_location(util::UniqueFunction<void(std::optional<app::AppError>)>&&) override
178
        REQUIRES(!m_mutex);
179
    void request_access_token(util::UniqueFunction<void(std::optional<app::AppError>)>&&) override REQUIRES(!m_mutex);
180

181
    void track_realm(std::string_view path) override REQUIRES(!m_mutex);
182
    std::string create_file_action(SyncFileAction action, std::string_view original_path,
183
                                   std::optional<std::string> requested_recovery_dir) override REQUIRES(!m_mutex);
184

185
    // ------------------------------------------------------------------------
186
    // SDK public API
187

188
    /// Returns true if the user's only identity is anonymous.
189
    bool is_anonymous() const REQUIRES(!m_mutex);
190

191
    std::string device_id() const REQUIRES(!m_mutex);
192
    bool has_device_id() const REQUIRES(!m_mutex);
193
    UserProfile user_profile() const REQUIRES(!m_mutex);
194
    std::vector<UserIdentity> identities() const REQUIRES(!m_mutex);
195

196
    // Custom user data embedded in the access token.
197
    std::optional<bson::BsonDocument> custom_data() const REQUIRES(!m_mutex);
198

199
    // Get the app instance that this user belongs to.
200
    std::shared_ptr<app::App> app() const REQUIRES(!m_mutex);
201

202
    /// Retrieves a general-purpose service client for the Realm Cloud service
203
    /// @param service_name The name of the cluster
204
    app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex);
205

206
    // Log the user out and mark it as such. This will also close its associated Sessions.
207
    void log_out() REQUIRES(!m_mutex);
208

209
    // Get the default path for a Realm for the given configuration.
210
    // The default value is `<rootDir>/<appId>/<userId>/<partitionValue>.realm`.
211
    // If the file cannot be created at this location, for example due to path length restrictions,
212
    // this function may pass back `<rootDir>/<hashedFileName>.realm`
213
    std::string path_for_realm(const SyncConfig& config,
214
                               std::optional<std::string> custom_file_name = std::nullopt) const REQUIRES(!m_mutex);
215

216
    // ------------------------------------------------------------------------
217
    // All of the following are called by `RealmMetadataStore` and are public only for
218
    // testing purposes. SDKs should not call these directly in non-test code
219
    // or expose them in the public API.
220

221
    static std::shared_ptr<User> make(std::shared_ptr<app::App> app, std::string_view user_id)
222
    {
9,201✔
223
        return std::make_shared<User>(Private(), std::move(app), user_id);
9,201✔
224
    }
9,201✔
225

226
    User(Private, std::shared_ptr<app::App> app, std::string_view user_id);
227
    ~User();
228

229
    void update_backing_data(std::optional<UserData>&& data) REQUIRES(!m_mutex);
230
    void update_data_for_testing(util::FunctionRef<void(UserData&)>) REQUIRES(!m_mutex);
231
    void detach_and_tear_down() REQUIRES(!m_mutex);
232

233
    /// Refreshes the custom data for this user
234
    /// If `update_location` is true, the location metadata will be queried before the request
235
    void refresh_custom_data(bool update_location,
236
                             util::UniqueFunction<void(std::optional<app::AppError>)> completion_block)
237
        REQUIRES(!m_mutex);
238
    void refresh_custom_data(util::UniqueFunction<void(std::optional<app::AppError>)> completion_block)
239
        REQUIRES(!m_mutex);
240

241
    // Hook for testing access token timeouts
242
    void set_seconds_to_adjust_time_for_testing(int seconds)
243
    {
2✔
244
        m_seconds_to_adjust_time_for_testing.store(seconds);
2✔
245
    }
2✔
246

247
private:
248
    util::CheckedMutex m_mutex;
249
    std::shared_ptr<App> m_app GUARDED_BY(m_mutex);
250
    const std::string m_app_id;
251
    const std::string m_user_id;
252
    UserData m_data GUARDED_BY(m_mutex);
253
    std::atomic<int> m_seconds_to_adjust_time_for_testing = 0;
254

255
    bool do_is_anonymous() const REQUIRES(m_mutex);
256
};
257

258
} // namespace realm::app
259

260
namespace std {
261
template <>
262
struct hash<realm::app::UserIdentity> {
263
    size_t operator()(realm::app::UserIdentity const&) const;
264
};
265
} // namespace std
266

267
#endif // REALM_OS_SYNC_USER_HPP
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