Merge branch '2.0.x' of github.com:realm/realm-js into kneth/v1_v2-upgrade

This commit is contained in:
Kenneth Geisshirt 2017-09-25 16:04:43 +02:00
commit 49392bd4c6
4 changed files with 183 additions and 26 deletions

View File

@ -1,10 +1,17 @@
NEXT RELEASE
=============================================================
### Enhancements
* Add a callback function used to verify SSL certificates in the sync config.
* Throw exception with recovery configuration when upgrading from 1.x to 2.x.
2.0.0-rc10 Release notes (2017-9-19)
=============================================================
### Breaking changes
* Updating core (3.2.1), sync (2.0.0-rc23 - packaged under 2.0.0-rc22), object store.
### Enhancements
* Throw exception with recovery path when upgrading from 1.x to 2.x.
* Add support for sorting Lists and Results on values from linked objects.
### Bug fixes

View File

@ -93,7 +93,11 @@ class Realm {
constructor(config) {}
/**
<<<<<<< HEAD
* Open a realm asynchronously with a promise. If the realm is synced, it will be fully
=======
* Open a Realm asynchronously with a promise. If the Realm is synced, it will be fully
>>>>>>> 590f5845af4d8fd0bd82f280de4ec72cbcbba044
* synchronized before it is available.
* @param {Realm~Configuration} config
* @returns {ProgressPromise} - a promise that will be resolved with the realm instance when it's available.
@ -101,7 +105,11 @@ class Realm {
static open(config) {}
/**
<<<<<<< HEAD
* Open a realm asynchronously with a callback. If the realm is synced, it will be fully
=======
* Open a Realm asynchronously with a callback. If the Realm is synced, it will be fully
>>>>>>> 590f5845af4d8fd0bd82f280de4ec72cbcbba044
* synchronized before it is available.
* @param {Realm~Configuration} config
* @param {callback(error, realm)} - will be called when the realm is ready.
@ -294,10 +302,45 @@ Realm.defaultPath;
* child properties:
* - `user` - A `User` object obtained by calling `Realm.Sync.User.login`
* - `url` - A `string` which contains a valid Realm Sync url
* - `error` - A callback function which is called in error situations
* - `error` - A callback function which is called in error situations.
* The `error` callback can take up to four optional arguments: `message`, `isFatal`,
* `category`, and `code`.
* - `validate_ssl` - Indicating if SSL certificates must be validated
* - `ssl_trust_certificate_path` - A path where to find trusted SSL certificates
* The `error` callback can take up to four optional arguments: `message`, `isFatal`, `category`, and `code`.
* - `open_ssl_verify_callback` - A callback function used to accept or reject the server's
* SSL certificate. open_ssl_verify_callback is called with an object of type
* <code>
* {
* serverAddress: String,
* serverPort: Number,
* pemCertificate: String,
* acceptedByOpenSSL: Boolean,
* depth: Number
* }
* </code>
* The return value of open_ssl_verify_callback decides whether the certificate is accepted (true)
* or rejected (false). The open_ssl_verify_callback function is only respected on platforms where
* OpenSSL is used for the sync client, e.g. Linux. The open_ssl_verify_callback function is not
* allowed to throw exceptions. If the operations needed to verify the certificate lead to an exception,
* the exception must be caught explicitly before returning. The return value would typically be false
* in case of an exception.
*
* When the sync client has received the server's certificate chain, it presents every certificate in
* the chain to the open_ssl_verify_callback function. The depth argument specifies the position of the
* certificate in the chain. depth = 0 represents the actual server certificate. The root
* certificate has the highest depth. The certificate of highest depth will be presented first.
*
* acceptedByOpenSSL is true if OpenSSL has accepted the certificate, and false if OpenSSL has rejected it.
* It is generally safe to return true when acceptedByOpenSSL is true. If acceptedByOpenSSL is false, an
* independent verification should be made.
*
* One possible way of using the open_ssl_verify_callback function is to embed the known server certificate
* in the client and accept the presented certificate if and only if it is equal to the known certificate.
*
* The purpose of open_ssl_verify_callback is to enable custom certificate handling and to solve cases where
* OpenSSL erroneously rejects valid certificates possibly because OpenSSL doesn't have access to the
* proper trust certificates.
*
*/
/**

2
lib/index.d.ts vendored
View File

@ -342,12 +342,14 @@ declare namespace Realm.Sync {
}
type ErrorCallback = (message?: string, isFatal?: boolean, category?: string, code?: number) => void;
type SSLVerifyCallback = (serverAddress: string, serverPort: number, pemCertificate: string, preverifyOk: number, depth: number) => boolean;
interface SyncConfiguration {
user: User;
url: string;
validate_ssl?: boolean;
ssl_trust_certificate_path?: string;
ssl_verify_callback?: SSLVerifyCallback;
error?: ErrorCallback;
}

View File

@ -22,6 +22,8 @@
#include <map>
#include <set>
#include <regex>
#include <mutex>
#include <condition_variable>
#include "event_loop_dispatcher.hpp"
#include "platform.hpp"
@ -240,6 +242,98 @@ private:
const Protected<typename T::Function> m_func;
};
// An object of type SSLVerifyCallbackSyncThreadFunctor is registered with the sync client in order
// to verify SSL certificates. The SSLVerifyCallbackSyncThreadFunctor object's operator() is called
// on the sync client's event loop thread.
template <typename T>
class SSLVerifyCallbackSyncThreadFunctor {
public:
SSLVerifyCallbackSyncThreadFunctor(typename T::Context ctx, typename T::Function ssl_verify_func)
: m_ctx(Context<T>::get_global_context(ctx))
, m_func(ctx, ssl_verify_func)
, m_event_loop_dispatcher {SSLVerifyCallbackSyncThreadFunctor<T>::main_loop_handler}
, m_mutex{new std::mutex}
, m_cond_var{new std::condition_variable}
{
}
// This function is called on the sync client's event loop thread.
bool operator ()(const std::string& server_address, sync::Session::port_type server_port, const char* pem_data, size_t pem_size, int preverify_ok, int depth)
{
const std::string pem_certificate {pem_data, pem_size};
{
std::lock_guard<std::mutex> lock {*m_mutex};
m_ssl_certificate_callback_done = false;
}
// Dispatch the call to the main_loop_handler on the node.js thread.
m_event_loop_dispatcher(this, server_address, server_port, pem_certificate, preverify_ok, depth);
bool ssl_certificate_accepted = false;
{
// Wait for the return value of the callback function on the node.js main thread.
// The sync client blocks during this wait.
std::unique_lock<std::mutex> lock(*m_mutex);
m_cond_var->wait(lock, [this] { return this->m_ssl_certificate_callback_done; });
ssl_certificate_accepted = m_ssl_certificate_accepted;
}
return ssl_certificate_accepted;
}
// main_loop_handler is called on the node.js main thread.
// main_loop_handler calls the user callback (m_func) and sends the return value
// back to the sync client's event loop thread through a condition variable.
static void main_loop_handler(SSLVerifyCallbackSyncThreadFunctor<T>* this_object,
const std::string& server_address,
sync::Session::port_type server_port,
const std::string& pem_certificate,
int preverify_ok,
int depth)
{
HANDLESCOPE
const Protected<typename T::GlobalContext>& ctx = this_object->m_ctx;
typename T::Object ssl_certificate_object = Object<T>::create_empty(ctx);
Object<T>::set_property(ctx, ssl_certificate_object, "serverAddress", Value<T>::from_string(ctx, server_address));
Object<T>::set_property(ctx, ssl_certificate_object, "serverPort", Value<T>::from_number(ctx, double(server_port)));
Object<T>::set_property(ctx, ssl_certificate_object, "pemCertificate", Value<T>::from_string(ctx, pem_certificate));
Object<T>::set_property(ctx, ssl_certificate_object, "acceptedByOpenSSL", Value<T>::from_boolean(ctx, preverify_ok != 0));
Object<T>::set_property(ctx, ssl_certificate_object, "depth", Value<T>::from_number(ctx, double(depth)));
const int argc = 1;
typename T::Value arguments[argc] = { ssl_certificate_object };
typename T::Value ret_val = Function<T>::callback(ctx, this_object->m_func, typename T::Object(), 1, arguments);
bool ret_val_bool = Value<T>::to_boolean(ctx, ret_val);
{
std::lock_guard<std::mutex> lock {*this_object->m_mutex};
this_object->m_ssl_certificate_callback_done = true;
this_object->m_ssl_certificate_accepted = ret_val_bool;
}
this_object->m_cond_var->notify_one();
};
private:
const Protected<typename T::GlobalContext> m_ctx;
const Protected<typename T::Function> m_func;
EventLoopDispatcher<void(SSLVerifyCallbackSyncThreadFunctor<T>* this_object,
const std::string& server_address,
sync::Session::port_type server_port,
const std::string& pem_certificate,
int preverify_ok,
int depth)> m_event_loop_dispatcher;
bool m_ssl_certificate_callback_done = false;
bool m_ssl_certificate_accepted = false;
std::shared_ptr<std::mutex> m_mutex;
std::shared_ptr<std::condition_variable> m_cond_var;
};
template<typename T>
void UserClass<T>::session_for_on_disk_path(ContextType ctx, FunctionType, ObjectType this_object, size_t argc, const ValueType arguments[], ReturnValue &return_value) {
auto user = *get_internal<T, UserClass<T>>(this_object);
@ -527,12 +621,23 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
ssl_trust_certificate_path = util::none;
}
std::function<sync::Session::SSLVerifyCallback> ssl_verify_callback;
ValueType ssl_verify_func = Object::get_property(ctx, sync_config_object, "open_ssl_verify_callback");
if (!Value::is_undefined(ctx, ssl_verify_func)) {
SSLVerifyCallbackSyncThreadFunctor<T> ssl_verify_functor {ctx, Value::validated_to_function(ctx, ssl_verify_func)};
ssl_verify_callback = std::move(ssl_verify_functor);
}
// FIXME - use make_shared
config.sync_config = std::shared_ptr<SyncConfig>(new SyncConfig{shared_user, raw_realm_url,
SyncSessionStopPolicy::AfterChangesUploaded,
std::move(bind), std::move(error_handler),
nullptr, util::none,
client_validate_ssl, ssl_trust_certificate_path});
client_validate_ssl, ssl_trust_certificate_path,
std::move(ssl_verify_callback)});
config.schema_mode = SchemaMode::Additive;
config.path = realm::SyncManager::shared().path_for_realm(*shared_user, raw_realm_url);