Adding permission schema for query based sync (#2027)

* Adding permission schema when opening the Realm
* Adding permission schema implicitly for query based Realms
* Remove old code
* Remove outdated test
This commit is contained in:
Nabil Hachicha 2018-09-19 15:16:44 +01:00 committed by Kenneth Geisshirt
parent 6e58f1f6f3
commit 111e9c223f
6 changed files with 189 additions and 42 deletions

View File

@ -3,6 +3,8 @@ X.Y.Z Release notes
### Bug fixes
* Fixed the type definition for `Realm.Permissions.User`. Thanks to @apperside! ([#2012](https://github.com/realm/realm-js/issues/2012), since v2.3.0-beta.2)
* Previously, adding a schema definition (using `config.schema = [Dog, Person]` for example) will prevent the permission schema from being added for query based Realm. ([#2017](https://github.com/realm/realm-js/issues/2017), since v2.3.0).
* As part of including the permission schema implicitly when using query based Realm, the schema `Realm.Permissions.Realm` was missing, which may break any query including it. ([#2016](https://github.com/realm/realm-js/issues/2016), since v2.3.0)
* Fixed the type definition for `Realm.getPrivileges()`, `Realm.getPrivileges(className)` and `Realm.getPrivileges(object)`. ([#2030](https://github.com/realm/realm-js/pull/2030), since v2.2.14)
### Compatibility

View File

@ -74,15 +74,6 @@ module.exports = function(realmConstructor) {
return promise;
}
// For synced Realms we open the Realm without specifying the schema and then wait until
// the Realm has finished its initial sync with the server. We then reopen it with the correct
// schema. This avoids writing the schema to a potentially read-only Realm file, which would
// result in sync rejecting the writes. `_waitForDownload` ensures that the session is kept
// alive until our callback has returned, which prevents it from being torn down and recreated
// when we close the schemaless Realm and open it with the correct schema.
if (config.sync.fullSynchronization === false && config.schema === undefined) {
throw new Error('Query-based sync requires a schema.');
}
let syncSession;
let promise = new Promise((resolve, reject) => {
let realm = new realmConstructor(waitForDownloadConfig(config));

View File

@ -584,7 +584,6 @@ const instanceMethods = {
user: this,
url: realmUrl,
},
schema: [],
};
// Set query-based as the default setting if the user doesn't specified any other behaviour.
@ -592,16 +591,6 @@ const instanceMethods = {
defaultConfig.sync.fullSynchronization = false;
}
// Automatically add Permission classes to the schema if Query-based sync is enabled
if (defaultConfig.sync.fullSynchronization === false || (config && config.sync && config.sync.partial === true)) {
defaultConfig.schema = [
Realm.Permissions.Class,
Realm.Permissions.Permission,
Realm.Permissions.Role,
Realm.Permissions.User,
];
}
// Merge default configuration with user provided config. User defined properties should aways win.
// Doing the naive merge in JS break objects that are backed by native objects, so these needs to
// be merged manually. This is currently only `sync.user`.

View File

@ -501,6 +501,89 @@ void RealmClass<T>::constructor(ContextType ctx, ObjectType this_object, size_t
schema_updated = true;
}
#if REALM_ENABLE_SYNC
// Include permission schema for query-based sync
if (config.sync_config && config.sync_config->is_partial) {
std::vector<ObjectSchema> objectsSchema;
if (!config.schema) {
config.schema.emplace(realm::Schema(objectsSchema));
}
auto it = config.schema->find("__Class");
if (it == config.schema->end()) {
realm::ObjectSchema clazz = {"__Class", {
{"name", realm::PropertyType::String, Property::IsPrimary{true}},
{"permissions", realm::PropertyType::Object|realm::PropertyType::Array, "__Permission"}
}};
objectsSchema.emplace_back(std::move(clazz));
schema_updated = true;
}
it = config.schema->find("__Permission");
if (it == config.schema->end()) {
realm::ObjectSchema permission = {"__Permission", {
{"role", realm::PropertyType::Object|realm::PropertyType::Nullable, "__Role"},
{"canRead", realm::PropertyType::Bool},
{"canUpdate", realm::PropertyType::Bool},
{"canDelete", realm::PropertyType::Bool},
{"canSetPermissions", realm::PropertyType::Bool},
{"canQuery", realm::PropertyType::Bool},
{"canCreate", realm::PropertyType::Bool},
{"canModifySchema", realm::PropertyType::Bool}
}};
objectsSchema.emplace_back(std::move(permission));
// adding default values
std::map<std::string, Protected<ValueType>> object_defaults;
object_defaults.emplace("canRead", Protected<ValueType>(ctx, Value::from_boolean(ctx, false)));
object_defaults.emplace("canUpdate", Protected<ValueType>(ctx, Value::from_boolean(ctx, false)));
object_defaults.emplace("canDelete", Protected<ValueType>(ctx, Value::from_boolean(ctx, false)));
object_defaults.emplace("canSetPermissions", Protected<ValueType>(ctx, Value::from_boolean(ctx, false)));
object_defaults.emplace("canQuery", Protected<ValueType>(ctx, Value::from_boolean(ctx, false)));
object_defaults.emplace("canCreate", Protected<ValueType>(ctx, Value::from_boolean(ctx, false)));
object_defaults.emplace("canModifySchema", Protected<ValueType>(ctx, Value::from_boolean(ctx, false)));
defaults.emplace("__Permission", std::move(object_defaults));
schema_updated = true;
}
it = config.schema->find("__Realm");
if (it == config.schema->end()) {
realm::ObjectSchema realm = {"__Realm", {
{"id", realm::PropertyType::Int, realm::Property::IsPrimary{true}},
{"permissions", realm::PropertyType::Object|realm::PropertyType::Array, "__Permission"}
}};
objectsSchema.emplace_back(std::move(realm));
schema_updated = true;
}
it = config.schema->find("__Role");
if (it == config.schema->end()) {
realm::ObjectSchema role = {"__Role", {
{"name", realm::PropertyType::String, realm::Property::IsPrimary{true}},
{"members", realm::PropertyType::Object|realm::PropertyType::Array, "__User"}
}};
objectsSchema.emplace_back(std::move(role));
schema_updated = true;
}
it = config.schema->find("__User");
if (it == config.schema->end()) {
realm::ObjectSchema user = {"__User", {
{"id", realm::PropertyType::String, realm::Property::IsPrimary{true}},
{"role", realm::PropertyType::Object|realm::PropertyType::Nullable, "__Role"}
}};
objectsSchema.emplace_back(std::move(user));
schema_updated = true;
}
objectsSchema.insert(objectsSchema.end(), std::make_move_iterator(config.schema->begin()),
std::make_move_iterator(config.schema->end()));
config.schema.emplace(realm::Schema(objectsSchema));
}
#endif
static const String schema_version_string = "schemaVersion";
ValueType version_value = Object::get_property(ctx, object, schema_version_string);
if (!Value::is_undefined(ctx, version_value)) {

View File

@ -231,5 +231,109 @@ module.exports = {
{read: true, update: false, delete: false, setPermissions: false});
realm.close();
});
},
testAddPermissionSchemaForQueryBasedRealmOnly() {
return new Promise((resolve, reject) => {
Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
let config = {
sync: {
user: user,
url: `realm://NO_SERVER/foo`,
fullSynchronization: false,
}
};
let realm = new Realm(config);
TestCase.assertTrue(realm.empty);
TestCase.assertEqual(realm.schema.length, 5);
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Class').length, 1);
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Permission').length, 1);
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Realm').length, 1);
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__Role').length, 1);
TestCase.assertEqual(realm.schema.filter(schema => schema.name === '__User').length, 1);
realm.close();
Realm.deleteFile(config);
// Full sync shouldn't include the permission schema
config = {
sync: {
user: user,
url: `realm://NO_SERVER/foo`,
fullSynchronization: true
}
};
realm = new Realm(config);
TestCase.assertTrue(realm.empty);
TestCase.assertEqual(realm.schema.length, 0);
realm.close();
Realm.deleteFile(config);
resolve();
}).catch(error => reject(error));
});
},
testUsingAddedPermissionSchemas() {
return new Promise((resolve, reject) => {
Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
const config = user.createConfiguration();
const PrivateChatRoomSchema = {
name: 'PrivateChatRoom',
primaryKey: 'name',
properties: {
'name': { type: 'string', optional: false },
'permissions': { type: 'list', objectType: '__Permission' }
}
};
config.schema = [PrivateChatRoomSchema];
const realm = new Realm(config);
let rooms = realm.objects(PrivateChatRoomSchema.name);
let subscription = rooms.subscribe();
subscription.addListener((sub, state) => {
if (state === Realm.Sync.SubscriptionState.Complete) {
let roles = realm.objects(Realm.Permissions.Role.schema.name).filtered(`name = '__User:${user.identity}'`);
TestCase.assertEqual(roles.length, 1);
realm.write(() => {
const permission = realm.create(Realm.Permissions.Permission.schema.name,
{ canUpdate: true, canRead: true, canQuery: true, role: roles[0] });
let room = realm.create(PrivateChatRoomSchema.name, { name: `#sales_${uuid()}` });
room.permissions.push(permission);
});
waitForUpload(realm).then(() => {
realm.close();
Realm.deleteFile(config);
// connecting with an empty schema should be possible, permission is added implicitly
Realm.open(user.createConfiguration()).then((realm) => {
let permissions = realm.objects(Realm.Permissions.Permission.schema.name).filtered(`role.name = '__User:${user.identity}'`);
let subscription = permissions.subscribe();
subscription.addListener((sub, state) => {
if (state === Realm.Sync.SubscriptionState.Complete) {
TestCase.assertEqual(permissions.length, 1);
TestCase.assertTrue(permissions[0].canRead);
TestCase.assertTrue(permissions[0].canQuery);
TestCase.assertTrue(permissions[0].canUpdate);
TestCase.assertFalse(permissions[0].canDelete);
TestCase.assertFalse(permissions[0].canSetPermissions);
TestCase.assertFalse(permissions[0].canCreate);
TestCase.assertFalse(permissions[0].canModifySchema);
realm.close();
resolve();
}
});
});
});
}
});
}).catch(error => reject(error));
});
}
}

View File

@ -824,13 +824,6 @@ module.exports = {
TestCase.assertThrows(() => Realm.automaticSyncConfiguration('foo', 'bar')); // too many arguments
}
function schemalessNotAllowed() {
let config = Realm.Sync.User.current.createConfiguration();
config.schema = undefined; // no schema in the configuration
Realm.deleteFile(config);
TestCase.assertThrows(() => { let realm = new Realm(config); } );
}
const credentials = Realm.Sync.Credentials.nickname(username);
return runOutOfProcess(__dirname + '/partial-sync-api-helper.js', username, REALM_MODULE_PATH)
.then(() => {
@ -841,7 +834,6 @@ module.exports = {
__partialIsNotAllowed();
shouldFail();
defaultRealmInvalidArguments();
schemalessNotAllowed();
return new Promise((resolve, reject) => {
let config = Realm.Sync.User.current.createConfiguration();
@ -1161,18 +1153,4 @@ module.exports = {
})
})
},
testOfflinePermissionSchemas() {
if (!isNodeProccess) {
return;
}
return Realm.Sync.User.login('http://localhost:9080', Realm.Sync.Credentials.anonymous()).then((u) => {
return new Promise((resolve, reject) => {
let realm = new Realm(u.createConfiguration());
TestCase.assertEqual(5, realm.objects(Realm.Permissions.Class.schema.name).length);
resolve('Done');
});
});
}
}