Merge branch 'master' of github.com:realm/realm-js into kneth/fix-ts-for-open_ssl_verify_callback

This commit is contained in:
Kenneth Geisshirt 2018-05-07 11:17:21 +02:00
commit dad5b3f521
22 changed files with 629 additions and 29 deletions

View File

@ -1,5 +1,11 @@
X.Y.Z Release notes
=============================================================
### Compatibility
* Sync protocol: 24
* Server-side history format: 4
* File format: 7
* Realm Object Server: 3.0.0 or later
### Breaking changes
* None.
@ -7,12 +13,37 @@ X.Y.Z Release notes
* None.
### Bug fixes
* Fixed logout error due to fetch body not being stringified (#1731).
* Fixed from TypeScript defintion for `open_ssl_verify_callback` configuration option (#1652).
### Internal
* Updated to Relm Sync 3.3.0.
* Updated to Realm Core 5.6.0.
2.4.0 Release notes (2018-4-26)
=============================================================
### Compatibility
* Sync protocol: 24
* Server-side history format: 4
* File format: 7
* Realm Object Server: 3.0.0 or later
### Breaking changes
* None.
### Enhancements
* Added password reset wrappers (#1699).
* Added a certificate validation using Android Keystore for RN (#1761).
### Bug fixes
* Fixed logout error due to fetch body not being stringified (#1731).
* Added `Subscription` import to `browser/index.js` and register type converter (#1711).
* Fixed call to `logout()` when debugging React Native apps (#1744).
### Internal
* Updated `scripts/test.sh` so it doesn't hang forever when the React tests fail to start (#1764).
2.3.4 Release notes (2018-4-12)
=============================================================
### Compatibility
@ -132,6 +163,82 @@ X.Y.Z Release notes
* Tested against Realm Object Server 3.0.0-alpha.8.
* Added `_disablePartialSyncUrlChecks` to `Realm.Configuration`.
2.2.20 Release notes (2018-4-13)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* None.
### Internal
* Updated to Realm Sync 2.2.17
2.2.19 Release notes (2018-4-10)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* Avoid crashing if partial Realms find their way into the admin Realm when using Realm Object Server v3.0.0 or later (realm-js-private #430).
### Internal
* None.
2.2.18 Release notes (2018-3-23)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* Fixed a bug where leaking Realms when an error occurs within an event handler (#1725).
### Internal
* Added trace logging to the global notifier (realm-js-private #426).
2.2.17 Release notes (2018-3-21)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* [Sync] Do a better job of not opening the notifier pipe file for global notifier realms.
### Internal
* None.
2.2.16 Release notes (2018-3-16)
=============================================================
### Breaking changes
* None.
### Enhancements
* None.
### Bug fixes
* [Sync] Avoid hammering the ROS authentication service when large numbers of Realms are opened at once.
### Internal
* None.
2.2.15 Release notes (2018-3-9)
=============================================================
### Breaking changes

View File

@ -1,5 +1,6 @@
PACKAGE_NAME=realm-js
VERSION=2.3.4
REALM_CORE_VERSION=5.4.0
REALM_SYNC_VERSION=3.0.0
VERSION=2.4.0
REALM_CORE_VERSION=5.6.0
REALM_SYNC_VERSION=3.3.0
REALM_OBJECT_SERVER_VERSION=3.0.0

View File

@ -270,6 +270,55 @@ class User {
*/
static register(server, username, password, callback) {}
/**
* Request a password reset email to be sent to a user's email.
* This will not throw an exception, even if the email doesn't belong to a Realm Object Server user.
*
* This can only be used for users who authenticated with the 'password' provider, and passed a valid email address as a username.
*
* @param {string} server - authentication server
* @param {string} email - The email that corresponds to the user's username.
* @return {Promise<void>} A promise which is resolved when the request has been sent.
*/
static requestPasswordReset(server, email) {}
/**
* Complete the password reset flow by using the reset token sent to the user's email as a one-time authorization token to change the password.
*
* By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where they can enter their new password.
* If you wish to provide a native UX, you may wish to modify the password authentication provider to use a custom URL with deep linking, so you can
* open the app, extract the token, and navigate to a view that allows to change the password within the app.
*
* @param {string} server - authentication server
* @param {string} reset_token - The token that was sent to the user's email address.
* @param {string} new_password - The user's new password.
* @return {Promise<void>} A promise which is resolved when the request has been sent.
*/
static completePasswordReset(server, reset_token, new_password) {}
/**
* Request an email confirmation email to be sent to a user's email.
* This will not throw an exception, even if the email doesn't belong to a Realm Object Server user.
*
* @param {string} server - authentication server
* @param {string} email - The email that corresponds to the user's username.
* @return {Promise<void>} A promise which is resolved when the request has been sent.
*/
static requestEmailConfirmation(server, email) {}
/**
* Complete the email confirmation flow by using the confirmation token sent to the user's email as a one-time authorization token to confirm their email.
*
* By default, Realm Object Server will send a link to the user's email that will redirect to a webpage where they can enter their new password.
* If you wish to provide a native UX, you may wish to modify the password authentication provider to use a custom URL with deep linking, so you can
* open the app, extract the token, and navigate to a view that allows to confirm the email within the app.
*
* @param {string} server - authentication server
* @param {string} confirmation_token - The token that was sent to the user's email address.
* @return {Promise<void>} A promise which is resolved when the request has been sent.
*/
static confirmEmail(server, confirmation_token) {}
/**
* Create an admin user for the given authentication server with an existing token
* @param {string} adminToken - existing admin token
@ -591,7 +640,7 @@ class Worker {
*/
class Adapter {
/**
* Create a new Adapter to moitor and process changes made across multiple Realms
* Create a new Adapter to monitor and process changes made across multiple Realms
* @param {string} localPath - the local path where realm files are stored
* @param {string} serverUrl - the sync server to listen to
* @param {SyncUser} adminUser - an admin user obtained by calling `new Realm.Sync.User.adminUser`

View File

@ -42,7 +42,7 @@ export default class User {
}
createMethods(User.prototype, objectTypes.USER, [
'logout',
'_logout',
'_sessionForOnDiskPath'
]);

8
lib/index.d.ts vendored
View File

@ -296,6 +296,14 @@ declare namespace Realm.Sync {
static registerWithProvider(server: string, options: { provider: string, providerToken: string, userInfo: any }, callback: (error: Error | null, user: User | null) => void): void;
static registerWithProvider(server: string, options: { provider: string, providerToken: string, userInfo: any }): Promise<Realm.Sync.User>;
static requestPasswordReset(server: string, email: string): Promise<void>;
static completePasswordReset(server:string, reset_token:string, new_password:string): Promise<void>;
static requestEmailConfirmation(server:string, email:string): Promise<void>;
static confirmEmail(server:string, confirmation_token:string): Promise<void>;
authenticate(server: string, provider: string, options: any): Promise<Realm.Sync.User>;
logout(): void;
openManagementRealm(): Realm;

View File

@ -25,7 +25,7 @@ function nodeRequire(module) {
return require_method(module);
}
const Realm = require('.');
const Realm = nodeRequire('.');
let impl;
process.on('message', (m) => {

View File

@ -85,7 +85,7 @@ class FunctionListener {
throw e;
});
}
};
}
class OutOfProcListener {
constructor(regex, regexStr, worker) {
@ -120,7 +120,7 @@ class OutOfProcListener {
}
this.worker.onchange(changes);
}
};
}
class Listener {
constructor(Sync, server, user) {
@ -231,7 +231,7 @@ class Listener {
}
this.initPromises = [];
}
};
}
let listener;
function addListener(server, user, regex, event, callback) {

View File

@ -275,6 +275,32 @@ function _authenticate(userConstructor, server, json, callback) {
}
}
function _updateAccount(userConstructor, server, json) {
const url = append_url(server, 'auth/password/updateAccount');
const options = {
method: 'POST',
body: JSON.stringify(json),
headers: postHeaders,
open_timeout: 5000
};
return performFetch(url, options)
.then((response) => {
const contentType = response.headers.get('Content-Type');
if (contentType.indexOf('application/json') === -1) {
return response.text().then((body) => {
throw new AuthError({
title: `Could not update user account: Realm Object Server didn't respond with valid JSON`,
body,
});
});
}
if (!response.ok) {
return response.json().then((body) => Promise.reject(new AuthError(body)));
}
});
}
const staticMethods = {
get current() {
const allUsers = this.all;
@ -379,6 +405,51 @@ const staticMethods = {
return _authenticate(this, server, json)
},
requestPasswordReset(server, email) {
checkTypes(arguments, ['string', 'string']);
const json = {
provider_id: email,
data: { action: 'reset_password' }
};
return _updateAccount(this, server, json);
},
completePasswordReset(server, reset_token, new_password) {
checkTypes(arguments, ['string', 'string']);
const json = {
data: {
action: 'complete_reset',
token: reset_token,
new_password: new_password
}
};
return _updateAccount(this, server, json);
},
requestEmailConfirmation(server, email) {
checkTypes(arguments, ['string', 'string']);
const json = {
provider_id: email,
data: { action: 'request_email_confirmation' }
};
return _updateAccount(this, server, json);
},
confirmEmail(server, confirmation_token) {
checkTypes(arguments, ['string', 'string']);
const json = {
data: {
action: 'confirm_email',
token: confirmation_token
}
};
return _updateAccount(this, server, json);
},
_refreshAccessToken: refreshAccessToken,
};

View File

@ -123,6 +123,6 @@ class Worker {
const message = this._workQueue.shift();
worker.send(message);
}
};
}
module.exports = Worker;

View File

@ -1,7 +1,7 @@
{
"name": "realm",
"description": "Realm is a mobile database: an alternative to SQLite and key-value stores",
"version": "2.3.4",
"version": "2.4.0",
"license": "Apache-2.0",
"homepage": "https://realm.io",
"keywords": [

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 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.
*/
package io.realm.react;
import android.content.res.AssetManager;

View File

@ -1,3 +1,19 @@
/*
* Copyright 2018 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.
*/
package io.realm.react;
import com.facebook.react.ReactPackage;

View File

@ -0,0 +1,152 @@
/*
* Copyright 2018 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.
*/
package io.realm.react.util;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.KeyStore;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import okhttp3.internal.tls.OkHostnameVerifier;
public class SSLHelper {
private final static String TAG = "REALM SSLHelper";
// Holds the certificate chain (per hostname). We need to keep the order of each certificate
// according to it's depth in the chain. The depth of the last
// certificate is 0. The depth of the first certificate is chain
// length - 1.
private static HashMap<String, List<String>> ROS_CERTIFICATES_CHAIN;
// The default Android Trust Manager which uses the default KeyStore to
// validate the certificate chain.
private static X509TrustManager TRUST_MANAGER;
// Help transform a String PEM representation of the certificate, into
// X509Certificate format.
private static CertificateFactory CERTIFICATE_FACTORY;
// From Sync implementation:
// A recommended way of using the callback function is to return true
// if preverify_ok = 1 and depth > 0,
// always check the host name if depth = 0,
// and use an independent verification step if preverify_ok = 0.
//
// Another possible way of using the callback is to collect all the
// ROS_CERTIFICATES_CHAIN until depth = 0, and present the entire chain for
// independent verification.
//
// In this implementation we use the second method, since it's more suitable for
// the underlying Java API we need to call to validate the certificate chain.
public synchronized static boolean certificateVerifier(String serverAddress, String pemData, int depth) {
try {
if (ROS_CERTIFICATES_CHAIN == null) {
ROS_CERTIFICATES_CHAIN = new HashMap<>();
TRUST_MANAGER = systemDefaultTrustManager();
CERTIFICATE_FACTORY = CertificateFactory.getInstance("X.509");
}
if (!ROS_CERTIFICATES_CHAIN.containsKey(serverAddress)) {
ROS_CERTIFICATES_CHAIN.put(serverAddress, new ArrayList<String>());
}
ROS_CERTIFICATES_CHAIN.get(serverAddress).add(pemData);
if (depth == 0) {
// transform all PEM ROS_CERTIFICATES_CHAIN into Java X509
// with respecting the order/depth provided from Sync.
List<String> pemChain = ROS_CERTIFICATES_CHAIN.get(serverAddress);
int n = pemChain.size();
X509Certificate[] chain = new X509Certificate[n];
for (String pem : pemChain) {
// The depth of the last certificate is 0.
// The depth of the first certificate is chain length - 1.
chain[--n] = buildCertificateFromPEM(pem);
}
// verify the entire chain
try {
TRUST_MANAGER.checkServerTrusted(chain, "RSA");
// verify the hostname
boolean isValid = OkHostnameVerifier.INSTANCE.verify(serverAddress, chain[0]);
if (isValid) {
return true;
} else {
Log.e(TAG, "Can not verify the hostname for the host: " + serverAddress);
return false;
}
} catch (CertificateException e) {
Log.e(TAG, "Can not validate SSL chain certificate for the host: " + serverAddress, e);
return false;
} finally {
// don't keep the certificate chain in memory
ROS_CERTIFICATES_CHAIN.remove(serverAddress);
}
} else {
// return true, since the verification will happen for the entire chain
// when receiving the depth == 0 (host certificate)
return true;
}
} catch (Exception e) {
Log.e(TAG, "Error during certificate validation for host: " + serverAddress, e);
return false;
}
}
// Credit OkHttp https://github.com/square/okhttp/blob/e5c84e1aef9572adb493197c1b6c4e882aca085b/okhttp/src/main/java/okhttp3/OkHttpClient.java#L270
private static X509TrustManager systemDefaultTrustManager() {
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init((KeyStore) null);
TrustManager[] trustManagers = trustManagerFactory.getTrustManagers();
if (trustManagers.length != 1 || !(trustManagers[0] instanceof X509TrustManager)) {
throw new IllegalStateException("Unexpected default trust managers:"
+ Arrays.toString(trustManagers));
}
return (X509TrustManager) trustManagers[0];
} catch (GeneralSecurityException e) {
throw new AssertionError(); // The system has no TLS. Just give up.
}
}
private static X509Certificate buildCertificateFromPEM(String pem) throws IOException, CertificateException {
InputStream stream = null;
try {
stream = new ByteArrayInputStream(pem.getBytes("UTF-8"));
return (X509Certificate) CERTIFICATE_FACTORY.generateCertificate(stream);
} finally {
if (stream != null) {
stream.close();
}
}
}
}

View File

@ -45,6 +45,7 @@ LOCAL_SRC_FILES += src/rpc.cpp
LOCAL_SRC_FILES += src/jsc/jsc_init.cpp
LOCAL_SRC_FILES += src/jsc/jsc_value.cpp
LOCAL_SRC_FILES += src/android/io_realm_react_RealmReactModule.cpp
LOCAL_SRC_FILES += src/android/jni_utils.cpp
LOCAL_SRC_FILES += src/android/jsc_override.cpp
LOCAL_SRC_FILES += src/android/platform.cpp
LOCAL_SRC_FILES += src/object-store/src/impl/collection_change_builder.cpp

View File

@ -200,13 +200,7 @@
"type": "none",
"dependencies": [ "realm-core" ],
"link_settings": {
"conditions": [
["OS=='win'", {
"libraries": [ "-lrealm-sync<(debug_library_suffix)" ]
}, {
"libraries": [ "-lrealm-sync-node<(debug_library_suffix)" ]
}]
]
"libraries": [ "-lrealm-sync<(debug_library_suffix)" ],
},
"all_dependent_settings": {
"defines": [ "REALM_ENABLE_SYNC=1" ]
@ -259,6 +253,7 @@
"include_dirs": [ "<(vendor_dir)/include" ],
"library_dirs": [
"<(vendor_dir)/lib",
"<(vendor_dir)/lib64",
"<(vendor_dir)/osx"
]
},

View File

@ -147,7 +147,17 @@ xctest() {
echo "Launching application. (output is in $(pwd)/build/out.txt)"
xcrun simctl launch --console ${SIM_DEVICE_NAME} io.realm.$1 | tee $(pwd)/build/out.txt
testpid=$(xcrun simctl launch --stdout=$(pwd)/build/out.txt --stderr=$(pwd)/build/err.txt ${SIM_DEVICE_NAME} io.realm.$1 | grep -m1 -o '\d\+$')
tail -n +0 -f $(pwd)/build/out.txt &
stdoutpid=$!
tail -n +0 -f $(pwd)/build/err.txt &
stderrpid=$!
# `kill -0` checks if a signal can be sent to the pid without actually doing so
while kill -0 $testpid 2> /dev/null; do sleep 1; done
kill $stdoutpid
kill $stderrpid
echo "Shuttting down ${SIM_DEVICE_NAME} simulator. (device is not deleted. you can use it to debug the app)"
shutdown_ios_simulator
@ -171,7 +181,7 @@ setup_ios_simulator() {
delete_ios_simulator >/dev/null 2>&1
#parse devices
IOS_RUNTIME=$(xcrun simctl list runtimes | grep -m1 -o 'com.apple.CoreSimulator.SimRuntime.iOS.*' | sed 's/[()]//g')
IOS_RUNTIME=$(xcrun simctl list runtimes | grep -v unavailable | grep -m1 -o 'com.apple.CoreSimulator.SimRuntime.iOS.*' | sed 's/[()]//g')
echo using iOS Runtime ${IOS_RUNTIME} to create new simulator ${SIM_DEVICE_NAME}
#create new test simulator

View File

@ -969,7 +969,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2.3.4;
CURRENT_PROJECT_VERSION = 2.4.0;
CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES;
@ -1033,7 +1033,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 2.3.4;
CURRENT_PROJECT_VERSION = 2.4.0;
CXX = "$(SRCROOT)/../scripts/ccache-clang++.sh";
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;

View File

@ -23,17 +23,49 @@
#include "io_realm_react_RealmReactModule.h"
#include "rpc.hpp"
#include "platform.hpp"
#include "jni_utils.hpp"
using namespace realm::rpc;
using namespace realm::jni_util;
static RPCServer *s_rpc_server;
extern bool realmContextInjected;
jclass ssl_helper_class;
namespace realm {
// set the AssetManager used to access bundled files within the APK
void set_asset_manager(AAssetManager* assetManager);
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void*)
{
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}
else {
JniUtils::initialize(vm, JNI_VERSION_1_6);
}
// We do lookup the class in this Thread, since FindClass sometimes fails
// when issued from the sync client thread
ssl_helper_class = reinterpret_cast<jclass>(env->NewGlobalRef(env->FindClass("io/realm/react/util/SSLHelper")));
return JNI_VERSION_1_6;
}
JNIEXPORT void JNI_OnUnload(JavaVM* vm, void*)
{
JNIEnv* env;
if (vm->GetEnv((void**)&env, JNI_VERSION_1_6) != JNI_OK) {
return;
}
else {
env->DeleteLocalRef(ssl_helper_class);
JniUtils::release();
}
}
JNIEXPORT void JNICALL Java_io_realm_react_RealmReactModule_setDefaultRealmFileDirectory
(JNIEnv *env, jclass, jstring fileDir, jobject javaAssetManager)
{

50
src/android/jni_utils.cpp Normal file
View File

@ -0,0 +1,50 @@
/*
* Copyright 2018 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 "jni_utils.hpp"
#include <memory>
using namespace realm::jni_util;
static std::unique_ptr<JniUtils> s_instance;
void JniUtils::initialize(JavaVM* vm, jint vm_version) noexcept
{
s_instance = std::unique_ptr<JniUtils>(new JniUtils(vm, vm_version));
}
void JniUtils::release()
{
s_instance.release();
}
JNIEnv* JniUtils::get_env(bool attach_if_needed)
{
JNIEnv* env;
if (s_instance->m_vm->GetEnv(reinterpret_cast<void**>(&env), s_instance->m_vm_version) != JNI_OK) {
if (attach_if_needed) {
jint ret = s_instance->m_vm->AttachCurrentThread(reinterpret_cast<void**>(&env), nullptr);
}
}
return env;
}
void JniUtils::detach_current_thread()
{
s_instance->m_vm->DetachCurrentThread();
}

59
src/android/jni_utils.hpp Normal file
View File

@ -0,0 +1,59 @@
/*
* Copyright 2018 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_JNI_UTIL_JNI_UTILS_HPP
#define REALM_JNI_UTIL_JNI_UTILS_HPP
#include <jni.h>
#include <vector>
namespace realm {
namespace jni_util {
// Util functions for JNI.
class JniUtils {
public:
~JniUtils()
{
}
// Call this only once in JNI_OnLoad.
static void initialize(JavaVM* vm, jint vm_version) noexcept;
// Call this in JNI_OnUnload.
static void release();
// When attach_if_needed is false, returns the JNIEnv if there is one attached to this thread. Assert if there is
// none. When attach_if_needed is true, try to attach and return a JNIEnv if necessary.
static JNIEnv* get_env(bool attach_if_needed = false);
// Detach the current thread from the JVM. Only required for C++ threads that where attached in the first place.
// Failing to do so is a resource leak.
static void detach_current_thread();
private:
JniUtils(JavaVM* vm, jint vm_version) noexcept
: m_vm(vm)
, m_vm_version(vm_version)
{
}
JavaVM* m_vm;
jint m_vm_version;
};
} // namespace realm
} // namespace jni_util
#endif // REALM_JNI_UTIL_JNI_UTILS_HPP

View File

@ -33,6 +33,14 @@
#include "realm/util/logger.hpp"
#include "realm/util/uri.hpp"
#if REALM_ANDROID
#include <jni.h>
#include "./android/io_realm_react_RealmReactModule.h"
#include "./android/jni_utils.hpp"
extern jclass ssl_helper_class;
#endif
namespace realm {
namespace js {
@ -303,7 +311,6 @@ public:
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;
@ -824,7 +831,33 @@ void SyncClass<T>::populate_sync_config(ContextType ctx, ObjectType realm_constr
SSLVerifyCallbackSyncThreadFunctor<T> ssl_verify_functor {ctx, Value::validated_to_function(ctx, ssl_verify_func)};
ssl_verify_callback = std::move(ssl_verify_functor);
}
#if REALM_ANDROID
// For React Native Android, if the user didn't define the ssl_verify_callback, we provide a default
// implementation for him, otherwise all SSL validation will fail, since the Sync client doesn't have
// access to the Android Keystore.
// This default implementation will perform a JNI call to invoke a Java method defined at the `SSLHelper`
// to perform the certificate verification.
else {
auto ssl_verify_functor =
[](const std::string server_address, realm::sync::Session::port_type server_port,
const char* pem_data, size_t pem_size, int preverify_ok, int depth) {
JNIEnv* env = realm::jni_util::JniUtils::get_env(true);
static jmethodID java_certificate_verifier = env->GetStaticMethodID(ssl_helper_class, "certificateVerifier", "(Ljava/lang/String;Ljava/lang/String;I)Z");
jstring jserver_address = env->NewStringUTF(server_address.c_str());
// deep copy the pem_data into a string so DeleteLocalRef delete the local reference not the original const char
std::string pem(pem_data, pem_size);
jstring jpem = env->NewStringUTF(pem.c_str());
bool isValid = env->CallStaticBooleanMethod(ssl_helper_class, java_certificate_verifier,
jserver_address,
jpem, depth) == JNI_TRUE;
env->DeleteLocalRef(jserver_address);
env->DeleteLocalRef(jpem);
return isValid;
};
ssl_verify_callback = std::move(ssl_verify_functor);
}
#endif
bool is_partial = false;
ValueType partial_value = Object::get_property(ctx, sync_config_object, "partial");
if (!Value::is_undefined(ctx, partial_value)) {

View File

@ -751,7 +751,7 @@ module.exports = {
const realm = new Realm(config1);
TestCase.assertFalse(realm.isClosed);
realm.close();
};
}
function __partialIsNotAllowed() {
let config2 = {
@ -762,7 +762,7 @@ module.exports = {
}
};
TestCase.assertThrows(() => new Realm(config2));
};
}
function shouldFail() {
let config = {
@ -780,11 +780,11 @@ module.exports = {
TestCase.assertEqual(realm.objects('Dog').length, 0);
TestCase.assertThrows(function () { var subscription = realm.objects('Dog').filtered("name == 'Lassy 1'").subscribe(); } );
realm.close();
};
}
function defaultRealmInvalidArguments() {
TestCase.assertThrows(() => Realm.automaticSyncConfiguration('foo', 'bar')); // too many arguments
};
}
return runOutOfProcess(__dirname + '/partial-sync-api-helper.js', username, REALM_MODULE_PATH)