481 lines
20 KiB
JavaScript
481 lines
20 KiB
JavaScript
////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Copyright 2017 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.
|
|
//
|
|
////////////////////////////////////////////////////////////////////////////
|
|
|
|
'use strict';
|
|
|
|
var Realm = require('realm');
|
|
var TestCase = require('./asserts');
|
|
|
|
function uuid() {
|
|
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
|
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
|
return v.toString(16);
|
|
});
|
|
}
|
|
|
|
function createUsersWithTestRealms(count) {
|
|
const createUserWithTestRealm = () => {
|
|
return Realm.Sync.User
|
|
.login('http://localhost:9080', Realm.Sync.Credentials.anonymous())
|
|
.then(user => {
|
|
new Realm({sync: {user, url: 'realm://localhost:9080/~/test', fullSynchronization: true }}).close();
|
|
return user;
|
|
});
|
|
};
|
|
|
|
return Promise.all(Array.from({length: count}, createUserWithTestRealm));
|
|
}
|
|
|
|
function wait(t) {
|
|
return new Promise(resolve => setTimeout(resolve, t));
|
|
}
|
|
|
|
function repeatUntil(fn, predicate) {
|
|
let retries = 0
|
|
function check() {
|
|
if (retries > 3) {
|
|
return Promise.reject(new Error("operation timed out"));
|
|
}
|
|
++retries;
|
|
return fn().then(x => predicate(x) ? x : wait(100).then(check));
|
|
}
|
|
return check;
|
|
}
|
|
|
|
function subscribe(results) {
|
|
const subscription = results.subscribe();
|
|
return new Promise((resolve, reject) => {
|
|
subscription.addListener((subscription, state) => {
|
|
if (state === Realm.Sync.SubscriptionState.Complete) {
|
|
resolve();
|
|
}
|
|
else if (state === Realm.Sync.SubscriptionState.Error) {
|
|
reject();
|
|
}
|
|
});
|
|
setTimeout(() => reject("listener never called"), 5000);
|
|
});
|
|
}
|
|
|
|
function waitForUpload(realm) {
|
|
let session = realm.syncSession;
|
|
return new Promise(resolve => {
|
|
let callback = (transferred, total) => {
|
|
if (transferred === total) {
|
|
session.removeProgressNotification(callback);
|
|
resolve(realm);
|
|
}
|
|
};
|
|
session.addProgressNotification('upload', 'forCurrentlyOutstandingWork', callback);
|
|
});
|
|
}
|
|
|
|
function waitForDownload(realm) {
|
|
let session = realm.syncSession;
|
|
return new Promise(resolve => {
|
|
let callback = (transferred, total) => {
|
|
if (transferred === total) {
|
|
session.removeProgressNotification(callback);
|
|
resolve(realm);
|
|
}
|
|
};
|
|
session.addProgressNotification('download', 'forCurrentlyOutstandingWork', callback);
|
|
});
|
|
}
|
|
|
|
function permissionForPath(permissions, path) {
|
|
for (const permission of permissions) {
|
|
if (permission.path == path) {
|
|
return permission;
|
|
}
|
|
}
|
|
}
|
|
|
|
const getPartialRealm = () => {
|
|
const testID = uuid();
|
|
return Realm.Sync.User
|
|
.login('http://localhost:9080', Realm.Sync.Credentials.nickname("user-" + testID, true))
|
|
.then(user => {
|
|
const config = user.createConfiguration({
|
|
sync: {
|
|
url: 'realm://localhost:9080/test_' + testID,
|
|
fullSynchronization: false,
|
|
}
|
|
});
|
|
return Realm.open(config); // Creates the Realm on the server
|
|
})
|
|
.then(waitForUpload)
|
|
.then(waitForDownload)
|
|
};
|
|
|
|
const assertFullAccess = function(permission) {
|
|
TestCase.assertTrue(permission.canCreate);
|
|
TestCase.assertTrue(permission.canRead);
|
|
TestCase.assertTrue(permission.canUpdate);
|
|
TestCase.assertTrue(permission.canDelete);
|
|
TestCase.assertTrue(permission.canQuery);
|
|
TestCase.assertTrue(permission.canModifySchema);
|
|
TestCase.assertTrue(permission.canSetPermissions);
|
|
}
|
|
|
|
module.exports = {
|
|
testApplyAndGetGrantedPermissions() {
|
|
return createUsersWithTestRealms(1).then(([user]) => {
|
|
const path = `/${user.identity}/test`;
|
|
return user
|
|
.applyPermissions({userId: `${user.identity}`},
|
|
`/${user.identity}/test`, 'read')
|
|
.then(repeatUntil(() => user.getGrantedPermissions('any'),
|
|
permissions => {
|
|
let permission = permissionForPath(permissions, path);
|
|
return permission && !permission.mayWrite;
|
|
}))
|
|
.then(permissions => {
|
|
let permission = permissionForPath(permissions, path);
|
|
TestCase.assertDefined(permission);
|
|
TestCase.assertEqual(permission.mayRead, true);
|
|
TestCase.assertEqual(permission.mayWrite, false);
|
|
TestCase.assertEqual(permission.mayManage, false);
|
|
});
|
|
});
|
|
},
|
|
|
|
testOfferPermissions() {
|
|
return createUsersWithTestRealms(2).then(([user1, user2]) => {
|
|
const path = `/${user1.identity}/test`;
|
|
return user1.offerPermissions(`/${user1.identity}/test`, 'read')
|
|
.then(token => user2.acceptPermissionOffer(token))
|
|
.then(realmUrl => {
|
|
TestCase.assertEqual(realmUrl, path);
|
|
return realmUrl;
|
|
})
|
|
.then(repeatUntil(() => user2.getGrantedPermissions('any'),
|
|
permissions => permissions.length > 2
|
|
&& permissionForPath(permissions, path)))
|
|
.then(permissions => {
|
|
let permission = permissionForPath(permissions, path)
|
|
TestCase.assertDefined(permission);
|
|
TestCase.assertEqual(permission.mayRead, true);
|
|
TestCase.assertEqual(permission.mayWrite, false);
|
|
TestCase.assertEqual(permission.mayManage, false);
|
|
});
|
|
});
|
|
},
|
|
|
|
testInvalidatePermissionOffer() {
|
|
let user1, user2, token;
|
|
return createUsersWithTestRealms(2)
|
|
.then(users => {
|
|
user1 = users[0];
|
|
user2 = users[1];
|
|
return user1.offerPermissions(`/${user1.identity}/test`, 'read');
|
|
})
|
|
.then(t => {
|
|
token = t;
|
|
return user1.invalidatePermissionOffer(token);
|
|
})
|
|
// Since we don't yet support notification when the invalidation has
|
|
// gone through, wait for a bit and hope the server is done
|
|
// processing.
|
|
.then(() => wait(100))
|
|
.then(() => user2.acceptPermissionOffer(token))
|
|
// We want the call to fail, i.e. the catch() below should be
|
|
// called.
|
|
.then(() => {
|
|
throw new Error("User was able to accept an invalid permission offer token");
|
|
})
|
|
.catch(error => {
|
|
try {
|
|
TestCase.assertEqual(error.message, 'The permission offer is expired.');
|
|
TestCase.assertEqual(error.statusCode, 701);
|
|
}
|
|
catch (e) {
|
|
throw new Error(e);
|
|
}
|
|
});
|
|
},
|
|
|
|
testObjectPermissions() {
|
|
let config = (user, url) => {
|
|
return {
|
|
schema: [
|
|
{
|
|
name: 'Object',
|
|
properties: {
|
|
value: 'int',
|
|
permissions: '__Permission[]'
|
|
}
|
|
},
|
|
Realm.Permissions.Permission,
|
|
Realm.Permissions.User,
|
|
Realm.Permissions.Role,
|
|
],
|
|
sync: {user: user, url: url, fullSynchronization: false }
|
|
};
|
|
};
|
|
let owner, otherUser
|
|
return Realm.Sync.User
|
|
.login('http://localhost:9080', Realm.Sync.Credentials.nickname(uuid()))
|
|
.then(user => {
|
|
owner = user;
|
|
new Realm({sync: {user, url: 'realm://localhost:9080/default'}}).close();
|
|
return Realm.Sync.User.login('http://localhost:9080', Realm.Sync.Credentials.nickname(uuid()))
|
|
})
|
|
.then((user) => {
|
|
otherUser = user;
|
|
let realm = new Realm(config(owner, 'realm://localhost:9080/default'));
|
|
realm.write(() => {
|
|
let user = realm.create(Realm.Permissions.User, {id: otherUser.identity})
|
|
let role = realm.create(Realm.Permissions.Role, {name: 'reader'})
|
|
role.members.push(user)
|
|
|
|
let obj1 = realm.create('Object', {value: 1});
|
|
let obj2 = realm.create('Object', {value: 2});
|
|
obj2.permissions.push(realm.create(Realm.Permissions.Permission,
|
|
{role: role, canRead: true, canUpdate: false}))
|
|
});
|
|
return waitForUpload(realm).then(() => realm.close());
|
|
})
|
|
.then(() => Realm.open(config(otherUser, `realm://localhost:9080/default`)))
|
|
.then((realm) => subscribe(realm.objects('Object')).then(() => realm))
|
|
.then((realm) => {
|
|
// Should have full access to the Realm as a whole
|
|
TestCase.assertSimilar('object', realm.privileges(),
|
|
{read: true, update: true, modifySchema: true, setPermissions: true});
|
|
TestCase.assertSimilar('object', realm.privileges('Object'),
|
|
{read: true, update: true, create: true, subscribe: true, setPermissions: true});
|
|
// Verify that checking via constructor works too
|
|
TestCase.assertSimilar('object', realm.privileges(Realm.Permissions.User),
|
|
{read: true, update: true, create: true, subscribe: true, setPermissions: true});
|
|
|
|
// Should only be able to see the second object
|
|
let results = realm.objects('Object')
|
|
TestCase.assertEqual(results.length, 1);
|
|
TestCase.assertEqual(results[0].value, 2);
|
|
TestCase.assertSimilar('object', realm.privileges(results[0]),
|
|
{read: true, update: false, delete: false, setPermissions: false});
|
|
realm.close();
|
|
});
|
|
},
|
|
|
|
testAddPermissionSchemaForQueryBasedRealmOnly() {
|
|
return Realm.Sync.User.register('http://localhost:9080', uuid(), 'password').then((user) => {
|
|
let config = {
|
|
schema: [],
|
|
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 + 1); // 5 = see below, 1 = __ResultSets
|
|
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 = {
|
|
schema: [],
|
|
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);
|
|
});
|
|
},
|
|
|
|
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).filtered(`name = '__User:${user.identity}'`);
|
|
TestCase.assertEqual(roles.length, 1);
|
|
|
|
realm.write(() => {
|
|
const permission = realm.create(Realm.Permissions.Permission,
|
|
{ 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
|
|
return Realm.open(user.createConfiguration());
|
|
})
|
|
.then(waitForUpload)
|
|
.then(waitForDownload)
|
|
.then((realm) => {
|
|
let permissions = realm.objects(Realm.Permissions.Permission).filtered(`role.name = '__User:${user.identity}'`);
|
|
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));
|
|
});
|
|
},
|
|
|
|
testFindOrCreate_realmPermissions() {
|
|
return getPartialRealm().then(realm => {
|
|
let realmPermissions = realm.permissions();
|
|
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>" ]
|
|
realm.write(() => {
|
|
let permissions = realmPermissions.findOrCreate("foo");
|
|
TestCase.assertEqual("foo", permissions.role.name);
|
|
TestCase.assertEqual(0, permissions.role.members.length);
|
|
TestCase.assertFalse(permissions.canCreate);
|
|
TestCase.assertFalse(permissions.canRead);
|
|
TestCase.assertFalse(permissions.canUpdate);
|
|
TestCase.assertFalse(permissions.canDelete);
|
|
TestCase.assertFalse(permissions.canQuery);
|
|
TestCase.assertFalse(permissions.canModifySchema);
|
|
TestCase.assertFalse(permissions.canSetPermissions);
|
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:<xxx>", "foo" ]
|
|
});
|
|
});
|
|
},
|
|
|
|
testFindOrCreate_existingRole() {
|
|
return getPartialRealm().then(realm => {
|
|
realm.write(() => {
|
|
realm.create('__Role', {'name':'foo'});
|
|
});
|
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
|
|
|
let realmPermissions = realm.permissions();
|
|
realm.write(() => {
|
|
let permissions = realmPermissions.findOrCreate("foo");
|
|
TestCase.assertEqual("foo", permissions.role.name);
|
|
TestCase.assertFalse(permissions.canCreate);
|
|
TestCase.assertFalse(permissions.canRead);
|
|
TestCase.assertFalse(permissions.canUpdate);
|
|
TestCase.assertFalse(permissions.canDelete);
|
|
TestCase.assertFalse(permissions.canQuery);
|
|
TestCase.assertFalse(permissions.canModifySchema);
|
|
TestCase.assertFalse(permissions.canSetPermissions);
|
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
|
});
|
|
});
|
|
},
|
|
|
|
testFindOrCreate_classPermissions() {
|
|
return getPartialRealm().then(realm => {
|
|
let classPermissions = realm.permissions('__Class');
|
|
TestCase.assertEqual(2, realm.objects('__Role').length); // [ "everyone", "__User:xxx" ]
|
|
realm.write(() => {
|
|
let permissions = classPermissions.findOrCreate("foo");
|
|
TestCase.assertEqual("foo", permissions.role.name);
|
|
TestCase.assertEqual(0, permissions.role.members.length);
|
|
TestCase.assertFalse(permissions.canCreate);
|
|
TestCase.assertFalse(permissions.canRead);
|
|
TestCase.assertFalse(permissions.canUpdate);
|
|
TestCase.assertFalse(permissions.canDelete);
|
|
TestCase.assertFalse(permissions.canQuery);
|
|
TestCase.assertFalse(permissions.canModifySchema);
|
|
TestCase.assertFalse(permissions.canSetPermissions);
|
|
TestCase.assertEqual(3, realm.objects('__Role').length); // [ "everyone", "__User:xxx", "foo" ]
|
|
});
|
|
});
|
|
},
|
|
|
|
testFindOrCreate_throwsOutsideWrite() {
|
|
return getPartialRealm().then(realm => {
|
|
let realmPermissions = realm.permissions();
|
|
TestCase.assertThrows(() => realmPermissions.findOrCreate("foo"));
|
|
let classPermissions = realm.permissions('__Class');
|
|
TestCase.assertThrows(() => classPermissions.findOrCreate("foo"));
|
|
});
|
|
},
|
|
|
|
testPermissions_Realm: function() {
|
|
return getPartialRealm().then(realm => {
|
|
let permissions = realm.permissions();
|
|
TestCase.assertEqual(1, permissions.permissions.length);
|
|
let perm = permissions.permissions[0];
|
|
TestCase.assertEqual("everyone", perm.role.name);
|
|
assertFullAccess(perm);
|
|
});
|
|
},
|
|
|
|
testPermissions_Class: function() {
|
|
return getPartialRealm().then(realm => {
|
|
let permissions = realm.permissions('__Class');
|
|
TestCase.assertEqual('__Class', permissions.name)
|
|
TestCase.assertEqual(1, permissions.permissions.length);
|
|
let perm = permissions.permissions[0];
|
|
TestCase.assertEqual("everyone", perm.role.name);
|
|
TestCase.assertTrue(perm.canCreate);
|
|
TestCase.assertTrue(perm.canRead);
|
|
TestCase.assertTrue(perm.canUpdate);
|
|
TestCase.assertFalse(perm.canDelete);
|
|
TestCase.assertTrue(perm.canQuery);
|
|
TestCase.assertFalse(perm.canModifySchema);
|
|
TestCase.assertTrue(perm.canSetPermissions);
|
|
});
|
|
},
|
|
|
|
testPermissions_Class_InvalidClassArgument: function() {
|
|
return getPartialRealm().then(realm => {
|
|
TestCase.assertThrows(() => realm.permissions('foo'));
|
|
});
|
|
},
|
|
|
|
};
|