//////////////////////////////////////////////////////////////////////////// // // Copyright 2016 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. // //////////////////////////////////////////////////////////////////////////// #ifndef REALM_OS_SYNC_USER_HPP #define REALM_OS_SYNC_USER_HPP #include <realm/object-store/util/atomic_shared_ptr.hpp> #include <realm/object-store/util/bson/bson.hpp> #include <realm/object-store/sync/subscribable.hpp> #include <realm/util/checked_mutex.hpp> #include <realm/util/optional.hpp> #include <realm/table.hpp> #include <memory> #include <mutex> #include <string> #include <unordered_map> #include <vector> namespace realm { namespace app { struct AppError; class MongoClient; } // namespace app class SyncSession; class SyncManager; // A superclass that bindings can inherit from in order to store information // upon a `SyncUser` object. class SyncUserContext { public: virtual ~SyncUserContext() = default; }; using SyncUserContextFactory = util::UniqueFunction<std::shared_ptr<SyncUserContext>()>; // A struct that decodes a given JWT. struct RealmJWT { // The token being decoded from. std::string token; // When the token expires. int64_t expires_at = 0; // When the token was issued. int64_t issued_at = 0; // Custom user data embedded in the encoded token. util::Optional<bson::BsonDocument> user_data; explicit RealmJWT(const std::string& token); RealmJWT() = default; bool operator==(const RealmJWT& other) const { return token == other.token; } }; struct SyncUserProfile { // The full name of the user. util::Optional<std::string> name() const { if (m_data.find("name") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("name")); } // The email address of the user. util::Optional<std::string> email() const { if (m_data.find("email") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("email")); } // A URL to the user's profile picture. util::Optional<std::string> picture_url() const { if (m_data.find("picture_url") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("picture_url")); } // The first name of the user. util::Optional<std::string> first_name() const { if (m_data.find("first_name") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("first_name")); } // The last name of the user. util::Optional<std::string> last_name() const { if (m_data.find("last_name") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("last_name")); } // The gender of the user. util::Optional<std::string> gender() const { if (m_data.find("gender") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("gender")); } // The birthdate of the user. util::Optional<std::string> birthday() const { if (m_data.find("birthday") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("birthday")); } // The minimum age of the user. util::Optional<std::string> min_age() const { if (m_data.find("min_age") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("min_age")); } // The maximum age of the user. util::Optional<std::string> max_age() const { if (m_data.find("max_age") == m_data.end()) { return util::none; } return static_cast<std::string>(m_data.at("max_age")); } bson::Bson operator[](const std::string& key) const { return m_data.at(key); } bson::BsonDocument data() const { return m_data; } SyncUserProfile(bson::BsonDocument&& data) : m_data(std::move(data)) { } SyncUserProfile() = default; private: bson::BsonDocument m_data; }; // A struct that represents an identity that a `User` is linked to struct SyncUserIdentity { // the id of the identity std::string id; // the associated provider type of the identity std::string provider_type; SyncUserIdentity(const std::string& id, const std::string& provider_type); bool operator==(const SyncUserIdentity& other) const { return id == other.id && provider_type == other.provider_type; } bool operator!=(const SyncUserIdentity& other) const { return id != other.id || provider_type != other.provider_type; } }; // A `SyncUser` represents a single user account. Each user manages the sessions that // are associated with it. class SyncUser : public std::enable_shared_from_this<SyncUser>, public Subscribable<SyncUser> { friend class SyncSession; public: enum class State { LoggedOut, LoggedIn, Removed, }; // Don't use this directly; use the `SyncManager` APIs. Public for use with `make_shared`. SyncUser(std::string refresh_token, const std::string id, const std::string provider_type, std::string access_token, SyncUser::State state, const std::string device_id, SyncManager* sync_manager); ~SyncUser(); // Return a list of all sessions belonging to this user. std::vector<std::shared_ptr<SyncSession>> all_sessions() REQUIRES(!m_mutex); // Return a session for a given on disk path. // In most cases, bindings shouldn't expose this to consumers, since the on-disk // path for a synced Realm is an opaque implementation detail. This API is retained // for testing purposes, and for bindings for consumers that are servers or tools. std::shared_ptr<SyncSession> session_for_on_disk_path(const std::string& path) REQUIRES(!m_mutex); // Update the user's state and refresh/access tokens atomically in a Realm transaction. // If the user is transitioning between LoggedIn and LoggedOut, then the access_token and // refresh token must be empty, and likewise must not be empty if transitioning between // logged out and logged in. // Note that this is called by the SyncManager, and should not be directly called. void update_state_and_tokens(SyncUser::State state, const std::string& access_token, const std::string& refresh_token) REQUIRES(!m_mutex, !m_tokens_mutex); // Update the user's refresh token. If the user is logged out, it will log itself back in. // Note that this is called by the SyncManager, and should not be directly called. void update_refresh_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex); // Update the user's access token. If the user is logged out, it will log itself back in. // Note that this is called by the SyncManager, and should not be directly called. void update_access_token(std::string&& token) REQUIRES(!m_mutex, !m_tokens_mutex); // Update the user's profile. void update_user_profile(const SyncUserProfile& profile) REQUIRES(!m_mutex); // Update the user's identities. void update_identities(std::vector<SyncUserIdentity> identities) REQUIRES(!m_mutex); // Log the user out and mark it as such. This will also close its associated Sessions. void log_out() REQUIRES(!m_mutex, !m_tokens_mutex); /// Returns true id the users access_token and refresh_token are set. bool is_logged_in() const REQUIRES(!m_mutex, !m_tokens_mutex); const std::string& identity() const noexcept { return m_identity; } const std::string& provider_type() const noexcept { return m_provider_type; } const std::string& local_identity() const noexcept { return m_local_identity; } std::string access_token() const REQUIRES(!m_tokens_mutex); std::string refresh_token() const REQUIRES(!m_tokens_mutex); std::string device_id() const REQUIRES(!m_mutex); bool has_device_id() const REQUIRES(!m_mutex); SyncUserProfile user_profile() const REQUIRES(!m_mutex); std::vector<SyncUserIdentity> identities() const REQUIRES(!m_mutex); // Custom user data embedded in the access token. util::Optional<bson::BsonDocument> custom_data() const REQUIRES(!m_tokens_mutex); State state() const; void set_state(SyncUser::State state) REQUIRES(!m_mutex); std::shared_ptr<SyncUserContext> binding_context() const { return m_binding_context.load(); } // Register a session to this user. // A registered session will be bound at the earliest opportunity: either // immediately, or upon the user becoming Active. // Note that this is called by the SyncManager, and should not be directly called. void register_session(std::shared_ptr<SyncSession>) REQUIRES(!m_mutex); /// Refreshes the custom data for this user /// If update_location is true, the location metadata will be queried before the request void refresh_custom_data(bool update_location, util::UniqueFunction<void(util::Optional<app::AppError>)> completion_block) REQUIRES(!m_mutex); void refresh_custom_data(util::UniqueFunction<void(util::Optional<app::AppError>)> completion_block) REQUIRES(!m_mutex); /// Checks the expiry on the access token against the local time and if it is invalid or expires soon, returns /// true. bool access_token_refresh_required() const REQUIRES(!m_tokens_mutex); // Optionally set a context factory. If so, must be set before any sessions are created. static void set_binding_context_factory(SyncUserContextFactory factory); std::shared_ptr<SyncManager> sync_manager() const REQUIRES(!m_mutex); /// Retrieves a general-purpose service client for the Realm Cloud service /// @param service_name The name of the cluster app::MongoClient mongo_client(const std::string& service_name) REQUIRES(!m_mutex); void set_seconds_to_adjust_time_for_testing(int seconds) { m_seconds_to_adjust_time_for_testing.store(seconds); } /// Check the SyncUsers passed as argument have the same remote identity id. friend bool operator==(const SyncUser& lhs, const SyncUser& rhs) { return lhs.identity() == rhs.identity(); } friend bool operator!=(const SyncUser& lhs, const SyncUser& rhs) { return !(lhs == rhs); } protected: friend class SyncManager; void detach_from_sync_manager() REQUIRES(!m_mutex); private: static SyncUserContextFactory s_binding_context_factory; static std::mutex s_binding_context_factory_mutex; bool do_is_logged_in() const REQUIRES(m_tokens_mutex); std::vector<std::shared_ptr<SyncSession>> revive_sessions() REQUIRES(m_mutex); std::atomic<State> m_state GUARDED_BY(m_mutex); util::AtomicSharedPtr<SyncUserContext> m_binding_context; // A locally assigned UUID intended to provide a level of indirection for various features. std::string m_local_identity; // The auth provider used to login this user. const std::string m_provider_type; // Mark the user as invalid, since a fatal user-related error was encountered. void invalidate() REQUIRES(!m_mutex); mutable util::CheckedMutex m_mutex; // Set by the server. The unique ID of the user account on the Realm Application. const std::string m_identity; // Sessions are owned by the SyncManager, but the user keeps a map of weak references // to them. std::unordered_map<std::string, std::weak_ptr<SyncSession>> m_sessions; // Waiting sessions are those that should be asked to connect once this user is logged in. std::unordered_map<std::string, std::weak_ptr<SyncSession>> m_waiting_sessions; mutable util::CheckedMutex m_tokens_mutex; // The user's refresh token. RealmJWT m_refresh_token GUARDED_BY(m_tokens_mutex); // The user's access token. RealmJWT m_access_token GUARDED_BY(m_tokens_mutex); // The identities associated with this user. std::vector<SyncUserIdentity> m_user_identities GUARDED_BY(m_mutex); SyncUserProfile m_user_profile GUARDED_BY(m_mutex); const std::string m_device_id; SyncManager* m_sync_manager; std::atomic<int> m_seconds_to_adjust_time_for_testing = 0; }; } // namespace realm namespace std { template <> struct hash<realm::SyncUserIdentity> { size_t operator()(realm::SyncUserIdentity const&) const; }; } // namespace std #endif // REALM_OS_SYNC_USER_HPP