mirror of
https://github.com/status-im/realm-js.git
synced 2025-01-11 06:46:03 +00:00
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:
parent
6e58f1f6f3
commit
111e9c223f
@ -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
|
||||
|
@ -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));
|
||||
|
@ -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`.
|
||||
|
@ -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)) {
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user