added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / sync / sync_permission.cpp
1 ////////////////////////////////////////////////////////////////////////////
2 //
3 // Copyright 2017 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 "sync/sync_permission.hpp"
20
21 #include "impl/notification_wrapper.hpp"
22 #include "impl/object_accessor_impl.hpp"
23 #include "object_schema.hpp"
24 #include "property.hpp"
25
26 #include "sync/sync_config.hpp"
27 #include "sync/sync_manager.hpp"
28 #include "sync/sync_session.hpp"
29 #include "sync/sync_user.hpp"
30 #include "util/event_loop_signal.hpp"
31 #include "util/uuid.hpp"
32
33 #include <realm/query_expression.hpp>
34
35 using namespace realm;
36 using namespace std::chrono;
37
38 // MARK: - Utility
39
40 namespace {
41
42 // Make a handler that extracts either an exception pointer, or the string value
43 // of the property with the specified name.
44 Permissions::AsyncOperationHandler make_handler_extracting_property(std::string property,
45                                                                     Permissions::PermissionOfferCallback callback)
46 {
47     return [property=std::move(property),
48             callback=std::move(callback)](Object* object, std::exception_ptr exception) {
49         if (exception) {
50             callback(none, exception);
51         } else {
52             CppContext context;
53             auto token = any_cast<std::string>(object->get_property_value<util::Any>(context, property));
54             callback(util::make_optional<std::string>(std::move(token)), nullptr);
55         }
56     };
57 }
58
59 AccessLevel extract_access_level(Object& permission, CppContext& context)
60 {
61     auto may_manage = permission.get_property_value<util::Any>(context, "mayManage");
62     if (may_manage.has_value() && any_cast<bool>(may_manage))
63         return AccessLevel::Admin;
64
65     auto may_write = permission.get_property_value<util::Any>(context, "mayWrite");
66     if (may_write.has_value() && any_cast<bool>(may_write))
67         return AccessLevel::Write;
68
69     auto may_read = permission.get_property_value<util::Any>(context, "mayRead");
70     if (may_read.has_value() && any_cast<bool>(may_read))
71         return AccessLevel::Read;
72
73     return AccessLevel::None;
74 }
75
76 /// Turn a system time point value into the 64-bit integer representing ns since the Unix epoch.
77 int64_t ns_since_unix_epoch(const system_clock::time_point& point)
78 {
79     tm unix_epoch{};
80     unix_epoch.tm_year = 70;
81     time_t epoch_time = mktime(&unix_epoch);
82     auto epoch_point = system_clock::from_time_t(epoch_time);
83     return duration_cast<nanoseconds>(point - epoch_point).count();
84 }
85
86 } // anonymous namespace
87
88 // MARK: - Permission
89
90 Permission::Permission(Object& permission)
91 {
92     CppContext context;
93     path = any_cast<std::string>(permission.get_property_value<util::Any>(context, "path"));
94     access = extract_access_level(permission, context);
95     condition = Condition(any_cast<std::string>(permission.get_property_value<util::Any>(context, "userId")));
96     updated_at = any_cast<Timestamp>(permission.get_property_value<util::Any>(context, "updatedAt"));
97 }
98
99 Permission::Permission(std::string path, AccessLevel access, Condition condition, Timestamp updated_at)
100 : path(std::move(path))
101 , access(access)
102 , condition(std::move(condition))
103 , updated_at(std::move(updated_at))
104 { }
105
106 std::string Permission::description_for_access_level(AccessLevel level)
107 {
108     switch (level) {
109         case AccessLevel::None: return "none";
110         case AccessLevel::Read: return "read";
111         case AccessLevel::Write: return "write";
112         case AccessLevel::Admin: return "admin";
113     }
114     REALM_UNREACHABLE();
115 }
116
117 bool Permission::paths_are_equivalent(std::string path_1, std::string path_2,
118                                       const std::string& user_id_1, const std::string& user_id_2)
119 {
120     REALM_ASSERT_DEBUG(path_1.length() > 0);
121     REALM_ASSERT_DEBUG(path_2.length() > 0);
122     if (path_1 == path_2) {
123         // If both paths are identical and contain `/~/`, the user IDs must match.
124         return (path_1.find("/~/") == std::string::npos) || (user_id_1 == user_id_2);
125     }
126     // Make substitutions for the first `/~/` in the string.
127     size_t index = path_1.find("/~/");
128     if (index != std::string::npos)
129         path_1.replace(index + 1, 1, user_id_1);
130
131     index = path_2.find("/~/");
132     if (index != std::string::npos)
133         path_2.replace(index + 1, 1, user_id_2);
134
135     return path_1 == path_2;
136 }
137
138 // MARK: - Permissions
139
140 void Permissions::get_permissions(std::shared_ptr<SyncUser> user,
141                                   PermissionResultsCallback callback,
142                                   const ConfigMaker& make_config)
143 {
144     auto realm = Permissions::permission_realm(user, make_config);
145     auto table = ObjectStore::table_for_object_type(realm->read_group(), "Permission");
146     auto results = std::make_shared<_impl::NotificationWrapper<Results>>(std::move(realm), *table);
147
148     // `get_permissions` works by temporarily adding an async notifier to the permission Realm.
149     // This notifier will run the `async` callback until the Realm contains permissions or
150     // an error happens. When either of these two things happen, the notifier will be
151     // unregistered by nulling out the `results_wrapper` container.
152     auto async = [results, callback=std::move(callback)](CollectionChangeSet, std::exception_ptr ex) mutable {
153         if (ex) {
154             callback(Results(), ex);
155             results.reset();
156             return;
157         }
158         if (results->size() > 0) {
159             // We monitor the raw results. The presence of a `__management` Realm indicates
160             // that the permissions have been downloaded (hence, we wait until size > 0).
161             TableRef table = ObjectStore::table_for_object_type(results->get_realm()->read_group(), "Permission");
162             size_t col_idx = table->get_descriptor()->get_column_index("path");
163             auto query = !(table->column<StringData>(col_idx).ends_with("/__permission")
164                            || table->column<StringData>(col_idx).ends_with("/__perm")
165                            || table->column<StringData>(col_idx).ends_with("/__management"));
166             // Call the callback with our new permissions object. This object will exclude the
167             // private Realms.
168             callback(results->filter(std::move(query)), nullptr);
169             results.reset();
170         }
171     };
172     results->add_notification_callback(std::move(async));
173 }
174
175 void Permissions::set_permission(std::shared_ptr<SyncUser> user,
176                                  Permission permission,
177                                  PermissionChangeCallback callback,
178                                  const ConfigMaker& make_config)
179 {
180     auto props = AnyDict{
181         {"userId", permission.condition.user_id},
182         {"realmUrl", user->server_url() + permission.path},
183         {"mayRead", permission.access != AccessLevel::None},
184         {"mayWrite", permission.access == AccessLevel::Write || permission.access == AccessLevel::Admin},
185         {"mayManage", permission.access == AccessLevel::Admin},
186     };
187     if (permission.condition.type == Permission::Condition::Type::KeyValue) {
188         props.insert({"metadataKey", permission.condition.key_value.first});
189         props.insert({"metadataValue", permission.condition.key_value.second});
190     }
191     auto cb = [callback=std::move(callback)](Object*, std::exception_ptr exception) {
192         callback(exception);
193     };
194     perform_async_operation("PermissionChange", std::move(user), std::move(cb), std::move(props), make_config);
195 }
196
197 void Permissions::delete_permission(std::shared_ptr<SyncUser> user,
198                                     Permission permission,
199                                     PermissionChangeCallback callback,
200                                     const ConfigMaker& make_config)
201 {
202     permission.access = AccessLevel::None;
203     set_permission(std::move(user), std::move(permission), std::move(callback), make_config);
204 }
205
206 void Permissions::make_offer(std::shared_ptr<SyncUser> user,
207                              PermissionOffer offer,
208                              PermissionOfferCallback callback,
209                              const ConfigMaker& make_config)
210 {
211     auto props = AnyDict{
212         {"expiresAt", std::move(offer.expiration)},
213         {"userId", user->identity()},
214         {"realmUrl", user->server_url() + offer.path},
215         {"mayRead", offer.access != AccessLevel::None},
216         {"mayWrite", offer.access == AccessLevel::Write || offer.access == AccessLevel::Admin},
217         {"mayManage", offer.access == AccessLevel::Admin},
218     };
219     perform_async_operation("PermissionOffer",
220                             std::move(user),
221                             make_handler_extracting_property("token", std::move(callback)),
222                             std::move(props),
223                             make_config);
224 }
225
226 void Permissions::accept_offer(std::shared_ptr<SyncUser> user,
227                                const std::string& token,
228                                PermissionOfferCallback callback,
229                                const ConfigMaker& make_config)
230 {
231     perform_async_operation("PermissionOfferResponse",
232                             std::move(user),
233                             make_handler_extracting_property("realmUrl", std::move(callback)),
234                             AnyDict{ {"token", token} },
235                             make_config);
236 }
237
238 void Permissions::perform_async_operation(const std::string& object_type,
239                                           std::shared_ptr<SyncUser> user,
240                                           AsyncOperationHandler handler,
241                                           AnyDict additional_props,
242                                           const ConfigMaker& make_config)
243 {;
244     auto realm = Permissions::management_realm(std::move(user), make_config);
245     CppContext context;
246
247     // Get the current time.
248     int64_t ns_since_epoch = ns_since_unix_epoch(system_clock::now());
249     int64_t s_arg = ns_since_epoch / (int64_t)Timestamp::nanoseconds_per_second;
250     int32_t ns_arg = ns_since_epoch % Timestamp::nanoseconds_per_second;
251
252     auto props = AnyDict{
253         {"id", util::uuid_string()},
254         {"createdAt", Timestamp(s_arg, ns_arg)},
255         {"updatedAt", Timestamp(s_arg, ns_arg)},
256     };
257     props.insert(additional_props.begin(), additional_props.end());
258
259     // Write the permission object.
260     realm->begin_transaction();
261     auto raw = Object::create<util::Any>(context, realm, *realm->schema().find(object_type), std::move(props), false);
262     auto object = std::make_shared<_impl::NotificationWrapper<Object>>(std::move(raw));
263     realm->commit_transaction();
264
265     // Observe the permission object until the permission change has been processed or failed.
266     // The notifier is automatically unregistered upon the completion of the permission
267     // change, one way or another.
268     auto block = [object, handler=std::move(handler)](CollectionChangeSet, std::exception_ptr ex) mutable {
269         if (ex) {
270             handler(nullptr, ex);
271             object.reset();
272             return;
273         }
274
275         CppContext context;
276         auto status_code = object->get_property_value<util::Any>(context, "statusCode");
277         if (!status_code.has_value()) {
278             // Continue waiting for the sync server to complete the operation.
279             return;
280         }
281
282         // Determine whether an error happened or not.
283         if (auto code = any_cast<long long>(status_code)) {
284             // The permission change failed because an error was returned from the server.
285             auto status = object->get_property_value<util::Any>(context, "statusMessage");
286             std::string error_str = (status.has_value()
287                                      ? any_cast<std::string>(status)
288                                      : util::format("Error code: %1", code));
289             handler(nullptr, std::make_exception_ptr(PermissionActionException(error_str, code)));
290         }
291         else {
292             handler(object.get(), nullptr);
293         }
294         object.reset();
295     };
296     object->add_notification_callback(std::move(block));
297 }
298
299 SharedRealm Permissions::management_realm(std::shared_ptr<SyncUser> user, const ConfigMaker& make_config)
300 {
301     // FIXME: maybe we should cache the management Realm on the user, so we don't need to open it every time.
302     const auto realm_url = util::format("realm%1/~/__management", user->server_url().substr(4));
303     Realm::Config config = make_config(user, std::move(realm_url));
304     config.sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
305     config.schema = Schema{
306         {"PermissionChange", {
307             Property{"id",                PropertyType::String, Property::IsPrimary{true}},
308             Property{"createdAt",         PropertyType::Date},
309             Property{"updatedAt",         PropertyType::Date},
310             Property{"statusCode",        PropertyType::Int|PropertyType::Nullable},
311             Property{"statusMessage",     PropertyType::String|PropertyType::Nullable},
312             Property{"userId",            PropertyType::String},
313             Property{"metadataKey",       PropertyType::String|PropertyType::Nullable},
314             Property{"metadataValue",     PropertyType::String|PropertyType::Nullable},
315             Property{"metadataNameSpace", PropertyType::String|PropertyType::Nullable},
316             Property{"realmUrl",          PropertyType::String},
317             Property{"mayRead",           PropertyType::Bool|PropertyType::Nullable},
318             Property{"mayWrite",          PropertyType::Bool|PropertyType::Nullable},
319             Property{"mayManage",         PropertyType::Bool|PropertyType::Nullable},
320         }},
321         {"PermissionOffer", {
322             Property{"id",                PropertyType::String, Property::IsPrimary{true}},
323             Property{"createdAt",         PropertyType::Date},
324             Property{"updatedAt",         PropertyType::Date},
325             Property{"expiresAt",         PropertyType::Date|PropertyType::Nullable},
326             Property{"statusCode",        PropertyType::Int|PropertyType::Nullable},
327             Property{"statusMessage",     PropertyType::String|PropertyType::Nullable},
328             Property{"token",             PropertyType::String|PropertyType::Nullable},
329             Property{"realmUrl",          PropertyType::String},
330             Property{"mayRead",           PropertyType::Bool},
331             Property{"mayWrite",          PropertyType::Bool},
332             Property{"mayManage",         PropertyType::Bool},
333         }},
334         {"PermissionOfferResponse", {
335             Property{"id",                PropertyType::String, Property::IsPrimary{true}},
336             Property{"createdAt",         PropertyType::Date},
337             Property{"updatedAt",         PropertyType::Date},
338             Property{"statusCode",        PropertyType::Int|PropertyType::Nullable},
339             Property{"statusMessage",     PropertyType::String|PropertyType::Nullable},
340             Property{"token",             PropertyType::String},
341             Property{"realmUrl",          PropertyType::String|PropertyType::Nullable},
342         }},
343     };
344     config.schema_version = 0;
345     auto shared_realm = Realm::get_shared_realm(std::move(config));
346     user->register_management_session(shared_realm->config().path);
347     return shared_realm;
348 }
349
350 SharedRealm Permissions::permission_realm(std::shared_ptr<SyncUser> user, const ConfigMaker& make_config)
351 {
352     // FIXME: maybe we should cache the permission Realm on the user, so we don't need to open it every time.
353     const auto realm_url = util::format("realm%1/~/__permission", user->server_url().substr(4));
354     Realm::Config config = make_config(user, std::move(realm_url));
355     config.sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
356     config.schema = Schema{
357         {"Permission", {
358             {"updatedAt", PropertyType::Date},
359             {"userId", PropertyType::String},
360             {"path", PropertyType::String},
361             {"mayRead", PropertyType::Bool},
362             {"mayWrite", PropertyType::Bool},
363             {"mayManage", PropertyType::Bool},
364         }}
365     };
366     config.schema_version = 0;
367     auto shared_realm = Realm::get_shared_realm(std::move(config));
368     user->register_permission_session(shared_realm->config().path);
369     return shared_realm;
370 }