added iOS source code
[wl-app.git] / iOS / Pods / Realm / Realm / ObjectStore / src / sync / sync_permission.cpp
diff --git a/iOS/Pods/Realm/Realm/ObjectStore/src/sync/sync_permission.cpp b/iOS/Pods/Realm/Realm/ObjectStore/src/sync/sync_permission.cpp
new file mode 100644 (file)
index 0000000..d53dad0
--- /dev/null
@@ -0,0 +1,370 @@
+////////////////////////////////////////////////////////////////////////////
+//
+// Copyright 2017 Realm Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+////////////////////////////////////////////////////////////////////////////
+
+#include "sync/sync_permission.hpp"
+
+#include "impl/notification_wrapper.hpp"
+#include "impl/object_accessor_impl.hpp"
+#include "object_schema.hpp"
+#include "property.hpp"
+
+#include "sync/sync_config.hpp"
+#include "sync/sync_manager.hpp"
+#include "sync/sync_session.hpp"
+#include "sync/sync_user.hpp"
+#include "util/event_loop_signal.hpp"
+#include "util/uuid.hpp"
+
+#include <realm/query_expression.hpp>
+
+using namespace realm;
+using namespace std::chrono;
+
+// MARK: - Utility
+
+namespace {
+
+// Make a handler that extracts either an exception pointer, or the string value
+// of the property with the specified name.
+Permissions::AsyncOperationHandler make_handler_extracting_property(std::string property,
+                                                                    Permissions::PermissionOfferCallback callback)
+{
+    return [property=std::move(property),
+            callback=std::move(callback)](Object* object, std::exception_ptr exception) {
+        if (exception) {
+            callback(none, exception);
+        } else {
+            CppContext context;
+            auto token = any_cast<std::string>(object->get_property_value<util::Any>(context, property));
+            callback(util::make_optional<std::string>(std::move(token)), nullptr);
+        }
+    };
+}
+
+AccessLevel extract_access_level(Object& permission, CppContext& context)
+{
+    auto may_manage = permission.get_property_value<util::Any>(context, "mayManage");
+    if (may_manage.has_value() && any_cast<bool>(may_manage))
+        return AccessLevel::Admin;
+
+    auto may_write = permission.get_property_value<util::Any>(context, "mayWrite");
+    if (may_write.has_value() && any_cast<bool>(may_write))
+        return AccessLevel::Write;
+
+    auto may_read = permission.get_property_value<util::Any>(context, "mayRead");
+    if (may_read.has_value() && any_cast<bool>(may_read))
+        return AccessLevel::Read;
+
+    return AccessLevel::None;
+}
+
+/// Turn a system time point value into the 64-bit integer representing ns since the Unix epoch.
+int64_t ns_since_unix_epoch(const system_clock::time_point& point)
+{
+    tm unix_epoch{};
+    unix_epoch.tm_year = 70;
+    time_t epoch_time = mktime(&unix_epoch);
+    auto epoch_point = system_clock::from_time_t(epoch_time);
+    return duration_cast<nanoseconds>(point - epoch_point).count();
+}
+
+} // anonymous namespace
+
+// MARK: - Permission
+
+Permission::Permission(Object& permission)
+{
+    CppContext context;
+    path = any_cast<std::string>(permission.get_property_value<util::Any>(context, "path"));
+    access = extract_access_level(permission, context);
+    condition = Condition(any_cast<std::string>(permission.get_property_value<util::Any>(context, "userId")));
+    updated_at = any_cast<Timestamp>(permission.get_property_value<util::Any>(context, "updatedAt"));
+}
+
+Permission::Permission(std::string path, AccessLevel access, Condition condition, Timestamp updated_at)
+: path(std::move(path))
+, access(access)
+, condition(std::move(condition))
+, updated_at(std::move(updated_at))
+{ }
+
+std::string Permission::description_for_access_level(AccessLevel level)
+{
+    switch (level) {
+        case AccessLevel::None: return "none";
+        case AccessLevel::Read: return "read";
+        case AccessLevel::Write: return "write";
+        case AccessLevel::Admin: return "admin";
+    }
+    REALM_UNREACHABLE();
+}
+
+bool Permission::paths_are_equivalent(std::string path_1, std::string path_2,
+                                      const std::string& user_id_1, const std::string& user_id_2)
+{
+    REALM_ASSERT_DEBUG(path_1.length() > 0);
+    REALM_ASSERT_DEBUG(path_2.length() > 0);
+    if (path_1 == path_2) {
+        // If both paths are identical and contain `/~/`, the user IDs must match.
+        return (path_1.find("/~/") == std::string::npos) || (user_id_1 == user_id_2);
+    }
+    // Make substitutions for the first `/~/` in the string.
+    size_t index = path_1.find("/~/");
+    if (index != std::string::npos)
+        path_1.replace(index + 1, 1, user_id_1);
+
+    index = path_2.find("/~/");
+    if (index != std::string::npos)
+        path_2.replace(index + 1, 1, user_id_2);
+
+    return path_1 == path_2;
+}
+
+// MARK: - Permissions
+
+void Permissions::get_permissions(std::shared_ptr<SyncUser> user,
+                                  PermissionResultsCallback callback,
+                                  const ConfigMaker& make_config)
+{
+    auto realm = Permissions::permission_realm(user, make_config);
+    auto table = ObjectStore::table_for_object_type(realm->read_group(), "Permission");
+    auto results = std::make_shared<_impl::NotificationWrapper<Results>>(std::move(realm), *table);
+
+    // `get_permissions` works by temporarily adding an async notifier to the permission Realm.
+    // This notifier will run the `async` callback until the Realm contains permissions or
+    // an error happens. When either of these two things happen, the notifier will be
+    // unregistered by nulling out the `results_wrapper` container.
+    auto async = [results, callback=std::move(callback)](CollectionChangeSet, std::exception_ptr ex) mutable {
+        if (ex) {
+            callback(Results(), ex);
+            results.reset();
+            return;
+        }
+        if (results->size() > 0) {
+            // We monitor the raw results. The presence of a `__management` Realm indicates
+            // that the permissions have been downloaded (hence, we wait until size > 0).
+            TableRef table = ObjectStore::table_for_object_type(results->get_realm()->read_group(), "Permission");
+            size_t col_idx = table->get_descriptor()->get_column_index("path");
+            auto query = !(table->column<StringData>(col_idx).ends_with("/__permission")
+                           || table->column<StringData>(col_idx).ends_with("/__perm")
+                           || table->column<StringData>(col_idx).ends_with("/__management"));
+            // Call the callback with our new permissions object. This object will exclude the
+            // private Realms.
+            callback(results->filter(std::move(query)), nullptr);
+            results.reset();
+        }
+    };
+    results->add_notification_callback(std::move(async));
+}
+
+void Permissions::set_permission(std::shared_ptr<SyncUser> user,
+                                 Permission permission,
+                                 PermissionChangeCallback callback,
+                                 const ConfigMaker& make_config)
+{
+    auto props = AnyDict{
+        {"userId", permission.condition.user_id},
+        {"realmUrl", user->server_url() + permission.path},
+        {"mayRead", permission.access != AccessLevel::None},
+        {"mayWrite", permission.access == AccessLevel::Write || permission.access == AccessLevel::Admin},
+        {"mayManage", permission.access == AccessLevel::Admin},
+    };
+    if (permission.condition.type == Permission::Condition::Type::KeyValue) {
+        props.insert({"metadataKey", permission.condition.key_value.first});
+        props.insert({"metadataValue", permission.condition.key_value.second});
+    }
+    auto cb = [callback=std::move(callback)](Object*, std::exception_ptr exception) {
+        callback(exception);
+    };
+    perform_async_operation("PermissionChange", std::move(user), std::move(cb), std::move(props), make_config);
+}
+
+void Permissions::delete_permission(std::shared_ptr<SyncUser> user,
+                                    Permission permission,
+                                    PermissionChangeCallback callback,
+                                    const ConfigMaker& make_config)
+{
+    permission.access = AccessLevel::None;
+    set_permission(std::move(user), std::move(permission), std::move(callback), make_config);
+}
+
+void Permissions::make_offer(std::shared_ptr<SyncUser> user,
+                             PermissionOffer offer,
+                             PermissionOfferCallback callback,
+                             const ConfigMaker& make_config)
+{
+    auto props = AnyDict{
+        {"expiresAt", std::move(offer.expiration)},
+        {"userId", user->identity()},
+        {"realmUrl", user->server_url() + offer.path},
+        {"mayRead", offer.access != AccessLevel::None},
+        {"mayWrite", offer.access == AccessLevel::Write || offer.access == AccessLevel::Admin},
+        {"mayManage", offer.access == AccessLevel::Admin},
+    };
+    perform_async_operation("PermissionOffer",
+                            std::move(user),
+                            make_handler_extracting_property("token", std::move(callback)),
+                            std::move(props),
+                            make_config);
+}
+
+void Permissions::accept_offer(std::shared_ptr<SyncUser> user,
+                               const std::string& token,
+                               PermissionOfferCallback callback,
+                               const ConfigMaker& make_config)
+{
+    perform_async_operation("PermissionOfferResponse",
+                            std::move(user),
+                            make_handler_extracting_property("realmUrl", std::move(callback)),
+                            AnyDict{ {"token", token} },
+                            make_config);
+}
+
+void Permissions::perform_async_operation(const std::string& object_type,
+                                          std::shared_ptr<SyncUser> user,
+                                          AsyncOperationHandler handler,
+                                          AnyDict additional_props,
+                                          const ConfigMaker& make_config)
+{;
+    auto realm = Permissions::management_realm(std::move(user), make_config);
+    CppContext context;
+
+    // Get the current time.
+    int64_t ns_since_epoch = ns_since_unix_epoch(system_clock::now());
+    int64_t s_arg = ns_since_epoch / (int64_t)Timestamp::nanoseconds_per_second;
+    int32_t ns_arg = ns_since_epoch % Timestamp::nanoseconds_per_second;
+
+    auto props = AnyDict{
+        {"id", util::uuid_string()},
+        {"createdAt", Timestamp(s_arg, ns_arg)},
+        {"updatedAt", Timestamp(s_arg, ns_arg)},
+    };
+    props.insert(additional_props.begin(), additional_props.end());
+
+    // Write the permission object.
+    realm->begin_transaction();
+    auto raw = Object::create<util::Any>(context, realm, *realm->schema().find(object_type), std::move(props), false);
+    auto object = std::make_shared<_impl::NotificationWrapper<Object>>(std::move(raw));
+    realm->commit_transaction();
+
+    // Observe the permission object until the permission change has been processed or failed.
+    // The notifier is automatically unregistered upon the completion of the permission
+    // change, one way or another.
+    auto block = [object, handler=std::move(handler)](CollectionChangeSet, std::exception_ptr ex) mutable {
+        if (ex) {
+            handler(nullptr, ex);
+            object.reset();
+            return;
+        }
+
+        CppContext context;
+        auto status_code = object->get_property_value<util::Any>(context, "statusCode");
+        if (!status_code.has_value()) {
+            // Continue waiting for the sync server to complete the operation.
+            return;
+        }
+
+        // Determine whether an error happened or not.
+        if (auto code = any_cast<long long>(status_code)) {
+            // The permission change failed because an error was returned from the server.
+            auto status = object->get_property_value<util::Any>(context, "statusMessage");
+            std::string error_str = (status.has_value()
+                                     ? any_cast<std::string>(status)
+                                     : util::format("Error code: %1", code));
+            handler(nullptr, std::make_exception_ptr(PermissionActionException(error_str, code)));
+        }
+        else {
+            handler(object.get(), nullptr);
+        }
+        object.reset();
+    };
+    object->add_notification_callback(std::move(block));
+}
+
+SharedRealm Permissions::management_realm(std::shared_ptr<SyncUser> user, const ConfigMaker& make_config)
+{
+    // FIXME: maybe we should cache the management Realm on the user, so we don't need to open it every time.
+    const auto realm_url = util::format("realm%1/~/__management", user->server_url().substr(4));
+    Realm::Config config = make_config(user, std::move(realm_url));
+    config.sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
+    config.schema = Schema{
+        {"PermissionChange", {
+            Property{"id",                PropertyType::String, Property::IsPrimary{true}},
+            Property{"createdAt",         PropertyType::Date},
+            Property{"updatedAt",         PropertyType::Date},
+            Property{"statusCode",        PropertyType::Int|PropertyType::Nullable},
+            Property{"statusMessage",     PropertyType::String|PropertyType::Nullable},
+            Property{"userId",            PropertyType::String},
+            Property{"metadataKey",       PropertyType::String|PropertyType::Nullable},
+            Property{"metadataValue",     PropertyType::String|PropertyType::Nullable},
+            Property{"metadataNameSpace", PropertyType::String|PropertyType::Nullable},
+            Property{"realmUrl",          PropertyType::String},
+            Property{"mayRead",           PropertyType::Bool|PropertyType::Nullable},
+            Property{"mayWrite",          PropertyType::Bool|PropertyType::Nullable},
+            Property{"mayManage",         PropertyType::Bool|PropertyType::Nullable},
+        }},
+        {"PermissionOffer", {
+            Property{"id",                PropertyType::String, Property::IsPrimary{true}},
+            Property{"createdAt",         PropertyType::Date},
+            Property{"updatedAt",         PropertyType::Date},
+            Property{"expiresAt",         PropertyType::Date|PropertyType::Nullable},
+            Property{"statusCode",        PropertyType::Int|PropertyType::Nullable},
+            Property{"statusMessage",     PropertyType::String|PropertyType::Nullable},
+            Property{"token",             PropertyType::String|PropertyType::Nullable},
+            Property{"realmUrl",          PropertyType::String},
+            Property{"mayRead",           PropertyType::Bool},
+            Property{"mayWrite",          PropertyType::Bool},
+            Property{"mayManage",         PropertyType::Bool},
+        }},
+        {"PermissionOfferResponse", {
+            Property{"id",                PropertyType::String, Property::IsPrimary{true}},
+            Property{"createdAt",         PropertyType::Date},
+            Property{"updatedAt",         PropertyType::Date},
+            Property{"statusCode",        PropertyType::Int|PropertyType::Nullable},
+            Property{"statusMessage",     PropertyType::String|PropertyType::Nullable},
+            Property{"token",             PropertyType::String},
+            Property{"realmUrl",          PropertyType::String|PropertyType::Nullable},
+        }},
+    };
+    config.schema_version = 0;
+    auto shared_realm = Realm::get_shared_realm(std::move(config));
+    user->register_management_session(shared_realm->config().path);
+    return shared_realm;
+}
+
+SharedRealm Permissions::permission_realm(std::shared_ptr<SyncUser> user, const ConfigMaker& make_config)
+{
+    // FIXME: maybe we should cache the permission Realm on the user, so we don't need to open it every time.
+    const auto realm_url = util::format("realm%1/~/__permission", user->server_url().substr(4));
+    Realm::Config config = make_config(user, std::move(realm_url));
+    config.sync_config->stop_policy = SyncSessionStopPolicy::Immediately;
+    config.schema = Schema{
+        {"Permission", {
+            {"updatedAt", PropertyType::Date},
+            {"userId", PropertyType::String},
+            {"path", PropertyType::String},
+            {"mayRead", PropertyType::Bool},
+            {"mayWrite", PropertyType::Bool},
+            {"mayManage", PropertyType::Bool},
+        }}
+    };
+    config.schema_version = 0;
+    auto shared_realm = Realm::get_shared_realm(std::move(config));
+    user->register_permission_session(shared_realm->config().path);
+    return shared_realm;
+}