mirror of
https://github.com/status-im/realm-js.git
synced 2025-01-25 22:10:10 +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
|
### 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)
|
* 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)
|
* 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
|
### Compatibility
|
||||||
|
@ -74,15 +74,6 @@ module.exports = function(realmConstructor) {
|
|||||||
return promise;
|
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 syncSession;
|
||||||
let promise = new Promise((resolve, reject) => {
|
let promise = new Promise((resolve, reject) => {
|
||||||
let realm = new realmConstructor(waitForDownloadConfig(config));
|
let realm = new realmConstructor(waitForDownloadConfig(config));
|
||||||
|
@ -584,7 +584,6 @@ const instanceMethods = {
|
|||||||
user: this,
|
user: this,
|
||||||
url: realmUrl,
|
url: realmUrl,
|
||||||
},
|
},
|
||||||
schema: [],
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set query-based as the default setting if the user doesn't specified any other behaviour.
|
// 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;
|
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.
|
// 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
|
// 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`.
|
// 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;
|
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";
|
static const String schema_version_string = "schemaVersion";
|
||||||
ValueType version_value = Object::get_property(ctx, object, schema_version_string);
|
ValueType version_value = Object::get_property(ctx, object, schema_version_string);
|
||||||
if (!Value::is_undefined(ctx, version_value)) {
|
if (!Value::is_undefined(ctx, version_value)) {
|
||||||
|
@ -231,5 +231,109 @@ module.exports = {
|
|||||||
{read: true, update: false, delete: false, setPermissions: false});
|
{read: true, update: false, delete: false, setPermissions: false});
|
||||||
realm.close();
|
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
|
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);
|
const credentials = Realm.Sync.Credentials.nickname(username);
|
||||||
return runOutOfProcess(__dirname + '/partial-sync-api-helper.js', username, REALM_MODULE_PATH)
|
return runOutOfProcess(__dirname + '/partial-sync-api-helper.js', username, REALM_MODULE_PATH)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
@ -841,7 +834,6 @@ module.exports = {
|
|||||||
__partialIsNotAllowed();
|
__partialIsNotAllowed();
|
||||||
shouldFail();
|
shouldFail();
|
||||||
defaultRealmInvalidArguments();
|
defaultRealmInvalidArguments();
|
||||||
schemalessNotAllowed();
|
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let config = Realm.Sync.User.current.createConfiguration();
|
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