3074 lines
84 KiB
JavaScript
3074 lines
84 KiB
JavaScript
'use strict';
|
|
const Config = require('../lib/Config');
|
|
|
|
describe('Pointer Permissions', () => {
|
|
beforeEach(() => {
|
|
Config.get(Parse.applicationId).schemaCache.clear();
|
|
});
|
|
|
|
describe('using single user-pointers', () => {
|
|
it('should work with find', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
obj.set('owner', user);
|
|
obj2.set('owner', user2);
|
|
return Parse.Object.saveAll([obj, obj2]);
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
return schema.updateClass('AnObject', {}, { readUserFields: ['owner'] });
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user1', 'password');
|
|
})
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.find();
|
|
})
|
|
.then(res => {
|
|
expect(res.length).toBe(1);
|
|
expect(res[0].id).toBe(obj.id);
|
|
done();
|
|
})
|
|
.catch(error => {
|
|
fail(JSON.stringify(error));
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should work with write', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
obj.set('owner', user);
|
|
obj.set('reader', user2);
|
|
obj2.set('owner', user2);
|
|
obj2.set('reader', user);
|
|
return Parse.Object.saveAll([obj, obj2]);
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
writeUserFields: ['owner'],
|
|
readUserFields: ['reader', 'owner'],
|
|
}
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user1', 'password');
|
|
})
|
|
.then(() => {
|
|
obj2.set('hello', 'world');
|
|
return obj2.save();
|
|
})
|
|
.then(
|
|
() => {
|
|
fail('User should not be able to update obj2');
|
|
},
|
|
err => {
|
|
// User 1 should not be able to update obj2
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
return Promise.resolve();
|
|
}
|
|
)
|
|
.then(() => {
|
|
obj.set('hello', 'world');
|
|
return obj.save();
|
|
})
|
|
.then(
|
|
() => {
|
|
return Parse.User.logIn('user2', 'password');
|
|
},
|
|
() => {
|
|
fail('User should be able to update');
|
|
return Promise.resolve();
|
|
}
|
|
)
|
|
.then(
|
|
() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.find();
|
|
},
|
|
() => {
|
|
fail('should login with user 2');
|
|
}
|
|
)
|
|
.then(
|
|
res => {
|
|
expect(res.length).toBe(2);
|
|
res.forEach(result => {
|
|
if (result.id == obj.id) {
|
|
expect(result.get('hello')).toBe('world');
|
|
} else {
|
|
expect(result.id).toBe(obj2.id);
|
|
}
|
|
});
|
|
done();
|
|
},
|
|
() => {
|
|
fail('failed');
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should let a proper user find', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
user
|
|
.signUp()
|
|
.then(() => {
|
|
return user2.signUp();
|
|
})
|
|
.then(() => {
|
|
Parse.User.logOut();
|
|
})
|
|
.then(() => {
|
|
obj.set('owner', user);
|
|
return Parse.Object.saveAll([obj, obj2]);
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{ find: {}, get: {}, readUserFields: ['owner'] }
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.find();
|
|
})
|
|
.then(res => {
|
|
expect(res.length).toBe(0);
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user2', 'password');
|
|
})
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.find();
|
|
})
|
|
.then(res => {
|
|
expect(res.length).toBe(0);
|
|
const q = new Parse.Query('AnObject');
|
|
return q.get(obj.id);
|
|
})
|
|
.then(
|
|
() => {
|
|
fail('User 2 should not get the obj1 object');
|
|
},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
expect(err.message).toBe('Object not found.');
|
|
return Promise.resolve();
|
|
}
|
|
)
|
|
.then(() => {
|
|
return Parse.User.logIn('user1', 'password');
|
|
})
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.find();
|
|
})
|
|
.then(res => {
|
|
expect(res.length).toBe(1);
|
|
done();
|
|
})
|
|
.catch(err => {
|
|
jfail(err);
|
|
fail('should not fail');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it_id('f38c35e7-d804-4d32-986d-2579e25d2461')(it)('should query on pointer permission enabled column', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
user
|
|
.signUp()
|
|
.then(() => {
|
|
return user2.signUp();
|
|
})
|
|
.then(() => {
|
|
Parse.User.logOut();
|
|
})
|
|
.then(() => {
|
|
obj.set('owner', user);
|
|
return Parse.Object.saveAll([obj, obj2]);
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{ find: {}, get: {}, readUserFields: ['owner'] }
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user1', 'password');
|
|
})
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
q.equalTo('owner', user2);
|
|
return q.find();
|
|
})
|
|
.then(res => {
|
|
expect(res.length).toBe(0);
|
|
done();
|
|
})
|
|
.catch(err => {
|
|
jfail(err);
|
|
fail('should not fail');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should not allow creating objects', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
user
|
|
.save()
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
return schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_User' } },
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owner'],
|
|
readUserFields: ['owner'],
|
|
}
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user1', 'password');
|
|
})
|
|
.then(() => {
|
|
obj.set('owner', user);
|
|
return obj.save();
|
|
})
|
|
.then(
|
|
() => {
|
|
fail('should not succeed');
|
|
done();
|
|
},
|
|
err => {
|
|
expect(err.code).toBe(119);
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should handle multiple writeUserFields', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
obj.set('owner', user);
|
|
obj.set('otherOwner', user2);
|
|
return obj.save();
|
|
})
|
|
.then(() => config.database.loadSchema())
|
|
.then(schema =>
|
|
schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{ find: { '*': true }, writeUserFields: ['owner', 'otherOwner'] }
|
|
)
|
|
)
|
|
.then(() => Parse.User.logIn('user1', 'password'))
|
|
.then(() => obj.save({ hello: 'fromUser1' }))
|
|
.then(() => Parse.User.logIn('user2', 'password'))
|
|
.then(() => obj.save({ hello: 'fromUser2' }))
|
|
.then(() => Parse.User.logOut())
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.first();
|
|
})
|
|
.then(result => {
|
|
expect(result.get('hello')).toBe('fromUser2');
|
|
done();
|
|
})
|
|
.catch(() => {
|
|
fail('should not fail');
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should prevent creating pointer permission on missing field', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
config.database
|
|
.loadSchema()
|
|
.then(schema => {
|
|
return schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owner'],
|
|
readUserFields: ['owner'],
|
|
}
|
|
);
|
|
})
|
|
.then(() => {
|
|
fail('should not succeed');
|
|
})
|
|
.catch(err => {
|
|
expect(err.code).toBe(107);
|
|
expect(err.message).toBe(
|
|
"'owner' is not a valid column for class level pointer permissions writeUserFields"
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should prevent creating pointer permission on bad field (of wrong type)', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
config.database
|
|
.loadSchema()
|
|
.then(schema => {
|
|
return schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owner: { type: 'String' } },
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owner'],
|
|
readUserFields: ['owner'],
|
|
}
|
|
);
|
|
})
|
|
.then(() => {
|
|
fail('should not succeed');
|
|
})
|
|
.catch(err => {
|
|
expect(err.code).toBe(107);
|
|
expect(err.message).toBe(
|
|
"'owner' is not a valid column for class level pointer permissions writeUserFields"
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should prevent creating pointer permission on bad field (non-user pointer)', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
config.database
|
|
.loadSchema()
|
|
.then(schema => {
|
|
return schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_Session' } },
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owner'],
|
|
readUserFields: ['owner'],
|
|
}
|
|
);
|
|
})
|
|
.then(() => {
|
|
fail('should not succeed');
|
|
})
|
|
.catch(err => {
|
|
expect(err.code).toBe(107);
|
|
expect(err.message).toBe(
|
|
"'owner' is not a valid column for class level pointer permissions writeUserFields"
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should prevent creating pointer permission on bad field (non-existing)', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('owner', 'value');
|
|
object
|
|
.save()
|
|
.then(() => {
|
|
return config.database.loadSchema();
|
|
})
|
|
.then(schema => {
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owner'],
|
|
readUserFields: ['owner'],
|
|
}
|
|
);
|
|
})
|
|
.then(() => {
|
|
fail('should not succeed');
|
|
})
|
|
.catch(err => {
|
|
expect(err.code).toBe(107);
|
|
expect(err.message).toBe(
|
|
"'owner' is not a valid column for class level pointer permissions writeUserFields"
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL write (PP Locked)', done => {
|
|
/*
|
|
tests:
|
|
CLP: update closed ({})
|
|
PointerPerm: "owner"
|
|
ACL: logged in user has access
|
|
|
|
The owner is another user than the ACL
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owner', user2);
|
|
return obj.save();
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass('AnObject', {}, { update: {}, writeUserFields: ['owner'] });
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user1', 'password');
|
|
})
|
|
.then(() => {
|
|
// user1 has ACL read/write but should be blocked by PP
|
|
return obj.save({ key: 'value' });
|
|
})
|
|
.then(
|
|
() => {
|
|
fail('Should not succeed saving');
|
|
done();
|
|
},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL write (ACL Locked)', done => {
|
|
/*
|
|
tests:
|
|
CLP: update closed ({})
|
|
PointerPerm: "owner"
|
|
ACL: logged in user has access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owner', user2);
|
|
return obj.save();
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass('AnObject', {}, { update: {}, writeUserFields: ['owner'] });
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user2', 'password');
|
|
})
|
|
.then(() => {
|
|
// user1 has ACL read/write but should be blocked by ACL
|
|
return obj.save({ key: 'value' });
|
|
})
|
|
.then(
|
|
() => {
|
|
fail('Should not succeed saving');
|
|
done();
|
|
},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', done => {
|
|
/*
|
|
tests:
|
|
CLP: update closed ({})
|
|
PointerPerm: "owner"
|
|
ACL: logged in user has access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
const ACL = new Parse.ACL();
|
|
ACL.setWriteAccess(user, true);
|
|
ACL.setWriteAccess(user2, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owner', user2);
|
|
return obj.save();
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass('AnObject', {}, { update: {}, writeUserFields: ['owner'] });
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user2', 'password');
|
|
})
|
|
.then(() => {
|
|
// user1 has ACL read/write but should be blocked by ACL
|
|
return obj.save({ key: 'value' });
|
|
})
|
|
.then(
|
|
objAgain => {
|
|
expect(objAgain.get('key')).toBe('value');
|
|
done();
|
|
},
|
|
() => {
|
|
fail('Should not fail saving');
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL read (PP locked)', done => {
|
|
/*
|
|
tests:
|
|
CLP: find/get open ({})
|
|
PointerPerm: "owner" : read
|
|
ACL: logged in user has access
|
|
|
|
The owner is another user than the ACL
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owner', user2);
|
|
return obj.save();
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{ find: {}, get: {}, readUserFields: ['owner'] }
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user1', 'password');
|
|
})
|
|
.then(() => {
|
|
// user1 has ACL read/write but should be block
|
|
return obj.fetch();
|
|
})
|
|
.then(
|
|
() => {
|
|
fail('Should not succeed saving');
|
|
done();
|
|
},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', done => {
|
|
/*
|
|
tests:
|
|
CLP: find/get open ({"*": true})
|
|
PointerPerm: "owner" : read
|
|
ACL: logged in user has access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
ACL.setReadAccess(user2, true);
|
|
ACL.setWriteAccess(user2, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owner', user2);
|
|
return obj.save();
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
find: { '*': true },
|
|
get: { '*': true },
|
|
readUserFields: ['owner'],
|
|
}
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user2', 'password');
|
|
})
|
|
.then(() => {
|
|
// user1 has ACL read/write but should be block
|
|
return obj.fetch();
|
|
})
|
|
.then(
|
|
objAgain => {
|
|
expect(objAgain.id).toBe(obj.id);
|
|
done();
|
|
},
|
|
() => {
|
|
fail('Should not fail fetching');
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL read (ACL locked)', done => {
|
|
/*
|
|
tests:
|
|
CLP: find/get open ({"*": true})
|
|
PointerPerm: "owner" : read // proper owner
|
|
ACL: logged in user has not access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
Parse.Object.saveAll([user, user2])
|
|
.then(() => {
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owner', user2);
|
|
return obj.save();
|
|
})
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
find: { '*': true },
|
|
get: { '*': true },
|
|
readUserFields: ['owner'],
|
|
}
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return Parse.User.logIn('user2', 'password');
|
|
})
|
|
.then(() => {
|
|
// user2 has ACL read/write but should be block by ACL
|
|
return obj.fetch();
|
|
})
|
|
.then(
|
|
() => {
|
|
fail('Should not succeed saving');
|
|
done();
|
|
},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should let master key find objects', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
return object
|
|
.save()
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_User' } },
|
|
{ find: {}, get: {}, readUserFields: ['owner'] }
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.find();
|
|
})
|
|
.then(
|
|
() => {},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
return Promise.resolve();
|
|
}
|
|
)
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.find({ useMasterKey: true });
|
|
})
|
|
.then(
|
|
objects => {
|
|
expect(objects.length).toBe(1);
|
|
done();
|
|
},
|
|
() => {
|
|
fail('master key should find the object');
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should let master key get objects', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
return object
|
|
.save()
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_User' } },
|
|
{ find: {}, get: {}, readUserFields: ['owner'] }
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.get(object.id);
|
|
})
|
|
.then(
|
|
() => {},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
return Promise.resolve();
|
|
}
|
|
)
|
|
.then(() => {
|
|
const q = new Parse.Query('AnObject');
|
|
return q.get(object.id, { useMasterKey: true });
|
|
})
|
|
.then(
|
|
objectAgain => {
|
|
expect(objectAgain).not.toBeUndefined();
|
|
expect(objectAgain.id).toBe(object.id);
|
|
done();
|
|
},
|
|
() => {
|
|
fail('master key should find the object');
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should let master key update objects', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
return object
|
|
.save()
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_User' } },
|
|
{ update: {}, writeUserFields: ['owner'] }
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return object.save({ hello: 'bar' });
|
|
})
|
|
.then(
|
|
() => {},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
return Promise.resolve();
|
|
}
|
|
)
|
|
.then(() => {
|
|
return object.save({ hello: 'baz' }, { useMasterKey: true });
|
|
})
|
|
.then(
|
|
objectAgain => {
|
|
expect(objectAgain.get('hello')).toBe('baz');
|
|
done();
|
|
},
|
|
() => {
|
|
fail('master key should save the object');
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should let master key delete objects', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
return object
|
|
.save()
|
|
.then(() => {
|
|
return config.database.loadSchema().then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.updateClass(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_User' } },
|
|
{ delete: {}, writeUserFields: ['owner'] }
|
|
);
|
|
});
|
|
})
|
|
.then(() => {
|
|
return object.destroy();
|
|
})
|
|
.then(
|
|
() => {
|
|
fail();
|
|
},
|
|
err => {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
return Promise.resolve();
|
|
}
|
|
)
|
|
.then(() => {
|
|
return object.destroy({ useMasterKey: true });
|
|
})
|
|
.then(
|
|
() => {
|
|
done();
|
|
},
|
|
() => {
|
|
fail('master key should destroy the object');
|
|
done();
|
|
}
|
|
);
|
|
});
|
|
|
|
it('should fail with invalid pointer perms (not array)', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
config.database
|
|
.loadSchema()
|
|
.then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_User' } },
|
|
{ delete: {}, writeUserFields: 'owner' }
|
|
);
|
|
})
|
|
.catch(err => {
|
|
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
|
done();
|
|
});
|
|
});
|
|
|
|
it('should fail with invalid pointer perms (non-existing field)', done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
config.database
|
|
.loadSchema()
|
|
.then(schema => {
|
|
// Lock the update, and let only owner write
|
|
return schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owner: { type: 'Pointer', targetClass: '_User' } },
|
|
{ delete: {}, writeUserFields: ['owner', 'invalid'] }
|
|
);
|
|
})
|
|
.catch(err => {
|
|
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('using arrays of user-pointers', () => {
|
|
it('should work with find', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2]);
|
|
|
|
obj.set('owners', [user]);
|
|
obj2.set('owners', [user2]);
|
|
await Parse.Object.saveAll([obj, obj2]);
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.updateClass('AnObject', {}, { readUserFields: ['owners'] });
|
|
|
|
await Parse.User.logIn('user1', 'password');
|
|
|
|
try {
|
|
const q = new Parse.Query('AnObject');
|
|
const res = await q.find();
|
|
expect(res.length).toBe(1);
|
|
expect(res[0].id).toBe(obj.id);
|
|
done();
|
|
} catch (err) {
|
|
done.fail(JSON.stringify(err));
|
|
}
|
|
});
|
|
|
|
it_id('1bbb9ed6-5558-4ce5-a238-b1a2015d273f')(it)('should work with write', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2]);
|
|
|
|
obj.set('owner', user);
|
|
obj.set('readers', [user2]);
|
|
obj2.set('owner', user2);
|
|
obj2.set('readers', [user]);
|
|
await Parse.Object.saveAll([obj, obj2]);
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
writeUserFields: ['owner'],
|
|
readUserFields: ['readers', 'owner'],
|
|
}
|
|
);
|
|
|
|
await Parse.User.logIn('user1', 'password');
|
|
|
|
obj2.set('hello', 'world');
|
|
try {
|
|
await obj2.save();
|
|
done.fail('User should not be able to update obj2');
|
|
} catch (err) {
|
|
// User 1 should not be able to update obj2
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
}
|
|
|
|
obj.set('hello', 'world');
|
|
try {
|
|
await obj.save();
|
|
} catch (err) {
|
|
done.fail('User should be able to update');
|
|
}
|
|
|
|
await Parse.User.logIn('user2', 'password');
|
|
|
|
try {
|
|
const q = new Parse.Query('AnObject');
|
|
const res = await q.find();
|
|
expect(res.length).toBe(2);
|
|
res.forEach(result => {
|
|
if (result.id == obj.id) {
|
|
expect(result.get('hello')).toBe('world');
|
|
} else {
|
|
expect(result.id).toBe(obj2.id);
|
|
}
|
|
});
|
|
done();
|
|
} catch (err) {
|
|
done.fail('failed');
|
|
}
|
|
});
|
|
|
|
it('should let a proper user find', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
|
|
await user.signUp();
|
|
await user2.signUp();
|
|
await user3.signUp();
|
|
await Parse.User.logOut();
|
|
|
|
obj.set('owners', [user, user2]);
|
|
await Parse.Object.saveAll([obj, obj2]);
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.updateClass('AnObject', {}, { find: {}, get: {}, readUserFields: ['owners'] });
|
|
|
|
let q = new Parse.Query('AnObject');
|
|
let result = await q.find();
|
|
expect(result.length).toBe(0);
|
|
|
|
Parse.User.logIn('user3', 'password');
|
|
q = new Parse.Query('AnObject');
|
|
result = await q.find();
|
|
|
|
expect(result.length).toBe(0);
|
|
q = new Parse.Query('AnObject');
|
|
|
|
try {
|
|
await q.get(obj.id);
|
|
done.fail('User 3 should not get the obj1 object');
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
expect(err.message).toBe('Object not found.');
|
|
}
|
|
|
|
for (const owner of ['user1', 'user2']) {
|
|
await Parse.User.logIn(owner, 'password');
|
|
try {
|
|
const q = new Parse.Query('AnObject');
|
|
result = await q.find();
|
|
expect(result.length).toBe(1);
|
|
} catch (err) {
|
|
done.fail('should not fail');
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it_id('8a7d188c-b75c-4eac-90b6-9b0b11f873ae')(it)('should query on pointer permission enabled column', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
|
|
await user.signUp();
|
|
await user2.signUp();
|
|
await user3.signUp();
|
|
await Parse.User.logOut();
|
|
|
|
obj.set('owners', [user, user2]);
|
|
await Parse.Object.saveAll([obj, obj2]);
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.updateClass('AnObject', {}, { find: {}, get: {}, readUserFields: ['owners'] });
|
|
|
|
for (const owner of ['user1', 'user2']) {
|
|
await Parse.User.logIn(owner, 'password');
|
|
try {
|
|
const q = new Parse.Query('AnObject');
|
|
q.equalTo('owners', user3);
|
|
const result = await q.find();
|
|
expect(result.length).toBe(0);
|
|
} catch (err) {
|
|
done.fail('should not fail');
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('should not query using arrays on pointer permission enabled column', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
const obj2 = new Parse.Object('AnObject');
|
|
|
|
await user.signUp();
|
|
await user2.signUp();
|
|
await user3.signUp();
|
|
await Parse.User.logOut();
|
|
|
|
obj.set('owners', [user, user2]);
|
|
await Parse.Object.saveAll([obj, obj2]);
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.updateClass('AnObject', {}, { find: {}, get: {}, readUserFields: ['owners'] });
|
|
|
|
for (const owner of ['user1', 'user2']) {
|
|
try {
|
|
await Parse.User.logIn(owner, 'password');
|
|
// Since querying for arrays is not supported this should throw an error
|
|
const q = new Parse.Query('AnObject');
|
|
q.equalTo('owners', [user3]);
|
|
await q.find();
|
|
done.fail('should fail');
|
|
// eslint-disable-next-line no-empty
|
|
} catch (error) {}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('should not allow creating objects', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
await Parse.Object.saveAll([user, user2]);
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owners: { type: 'Array' } },
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owners'],
|
|
readUserFields: ['owners'],
|
|
}
|
|
);
|
|
|
|
for (const owner of ['user1', 'user2']) {
|
|
await Parse.User.logIn(owner, 'password');
|
|
try {
|
|
obj.set('owners', [user, user2]);
|
|
await obj.save();
|
|
done.fail('should not succeed');
|
|
} catch (err) {
|
|
expect(err.code).toBe(119);
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('should handle multiple writeUserFields', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2]);
|
|
obj.set('owners', [user]);
|
|
obj.set('otherOwners', [user2]);
|
|
await obj.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{ find: { '*': true }, writeUserFields: ['owners', 'otherOwners'] }
|
|
);
|
|
|
|
await Parse.User.logIn('user1', 'password');
|
|
await obj.save({ hello: 'fromUser1' });
|
|
await Parse.User.logIn('user2', 'password');
|
|
await obj.save({ hello: 'fromUser2' });
|
|
await Parse.User.logOut();
|
|
|
|
try {
|
|
const q = new Parse.Query('AnObject');
|
|
const result = await q.first();
|
|
expect(result.get('hello')).toBe('fromUser2');
|
|
done();
|
|
} catch (err) {
|
|
done.fail('should not fail');
|
|
}
|
|
});
|
|
|
|
it('should prevent creating pointer permission on missing field', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const schema = await config.database.loadSchema();
|
|
try {
|
|
await schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owners'],
|
|
readUserFields: ['owners'],
|
|
}
|
|
);
|
|
done.fail('should not succeed');
|
|
} catch (err) {
|
|
expect(err.code).toBe(107);
|
|
expect(err.message).toBe(
|
|
"'owners' is not a valid column for class level pointer permissions writeUserFields"
|
|
);
|
|
done();
|
|
}
|
|
});
|
|
|
|
it('should prevent creating pointer permission on bad field (of wrong type)', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const schema = await config.database.loadSchema();
|
|
try {
|
|
await schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owners: { type: 'String' } },
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owners'],
|
|
readUserFields: ['owners'],
|
|
}
|
|
);
|
|
done.fail('should not succeed');
|
|
} catch (err) {
|
|
expect(err.code).toBe(107);
|
|
expect(err.message).toBe(
|
|
"'owners' is not a valid column for class level pointer permissions writeUserFields"
|
|
);
|
|
done();
|
|
}
|
|
});
|
|
|
|
it('should prevent creating pointer permission on bad field (non-existing)', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('owners', 'value');
|
|
await object.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
try {
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
create: {},
|
|
writeUserFields: ['owners'],
|
|
readUserFields: ['owners'],
|
|
}
|
|
);
|
|
done.fail('should not succeed');
|
|
} catch (err) {
|
|
expect(err.code).toBe(107);
|
|
expect(err.message).toBe(
|
|
"'owners' is not a valid column for class level pointer permissions writeUserFields"
|
|
);
|
|
done();
|
|
}
|
|
});
|
|
|
|
it('should work with arrays containing valid & invalid elements', async done => {
|
|
/* Since there is no way to check the validity of objects in arrays before querying invalid
|
|
elements in arrays should be ignored. */
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2]);
|
|
|
|
obj.set('owners', [user, '', -1, true, [], { invalid: -1 }]);
|
|
await Parse.Object.saveAll([obj]);
|
|
|
|
const schema = await config.database.loadSchema();
|
|
await schema.updateClass('AnObject', {}, { readUserFields: ['owners'] });
|
|
|
|
await Parse.User.logIn('user1', 'password');
|
|
|
|
try {
|
|
const q = new Parse.Query('AnObject');
|
|
const res = await q.find();
|
|
expect(res.length).toBe(1);
|
|
expect(res[0].id).toBe(obj.id);
|
|
} catch (err) {
|
|
done.fail(JSON.stringify(err));
|
|
}
|
|
|
|
await Parse.User.logOut();
|
|
await Parse.User.logIn('user2', 'password');
|
|
|
|
try {
|
|
const q = new Parse.Query('AnObject');
|
|
const res = await q.find();
|
|
expect(res.length).toBe(0);
|
|
done();
|
|
} catch (err) {
|
|
done.fail(JSON.stringify(err));
|
|
}
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL write (PP Locked)', async done => {
|
|
/*
|
|
tests:
|
|
CLP: update closed ({})
|
|
PointerPerm: "owners"
|
|
ACL: logged in user has access
|
|
|
|
The owner is another user than the ACL
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2, user3]);
|
|
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owners', [user2, user3]);
|
|
await obj.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Lock the update, and let only owners write
|
|
await schema.updateClass('AnObject', {}, { update: {}, writeUserFields: ['owners'] });
|
|
|
|
await Parse.User.logIn('user1', 'password');
|
|
try {
|
|
// user1 has ACL read/write but should be blocked by PP
|
|
await obj.save({ key: 'value' });
|
|
done.fail('Should not succeed saving');
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
done();
|
|
}
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL write (ACL Locked)', async done => {
|
|
/*
|
|
tests:
|
|
CLP: update closed ({})
|
|
PointerPerm: "owners"
|
|
ACL: logged in user has access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2, user3]);
|
|
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owners', [user2, user3]);
|
|
await obj.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Lock the update, and let only owners write
|
|
await schema.updateClass('AnObject', {}, { update: {}, writeUserFields: ['owners'] });
|
|
|
|
for (const owner of ['user2', 'user3']) {
|
|
await Parse.User.logIn(owner, 'password');
|
|
try {
|
|
await obj.save({ key: 'value' });
|
|
done.fail('Should not succeed saving');
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL write (ACL/PP OK)', async done => {
|
|
/*
|
|
tests:
|
|
CLP: update closed ({})
|
|
PointerPerm: "owners"
|
|
ACL: logged in user has access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2, user3]);
|
|
const ACL = new Parse.ACL();
|
|
ACL.setWriteAccess(user, true);
|
|
ACL.setWriteAccess(user2, true);
|
|
ACL.setWriteAccess(user3, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owners', [user2, user3]);
|
|
await obj.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Lock the update, and let only owners write
|
|
await schema.updateClass('AnObject', {}, { update: {}, writeUserFields: ['owners'] });
|
|
|
|
for (const owner of ['user2', 'user3']) {
|
|
await Parse.User.logIn(owner, 'password');
|
|
try {
|
|
const objectAgain = await obj.save({ key: 'value' });
|
|
expect(objectAgain.get('key')).toBe('value');
|
|
} catch (err) {
|
|
done.fail('Should not fail saving');
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL read (PP locked)', async done => {
|
|
/*
|
|
tests:
|
|
CLP: find/get open ({})
|
|
PointerPerm: "owners" : read
|
|
ACL: logged in user has access
|
|
|
|
The owner is another user than the ACL
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2, user3]);
|
|
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owners', [user2, user3]);
|
|
await obj.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Lock reading, and let only owners read
|
|
await schema.updateClass('AnObject', {}, { find: {}, get: {}, readUserFields: ['owners'] });
|
|
|
|
await Parse.User.logIn('user1', 'password');
|
|
try {
|
|
// user1 has ACL read/write but should be blocked
|
|
await obj.fetch();
|
|
done.fail('Should not succeed fetching');
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
done();
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL read (PP/ACL OK)', async done => {
|
|
/*
|
|
tests:
|
|
CLP: find/get open ({"*": true})
|
|
PointerPerm: "owners" : read
|
|
ACL: logged in user has access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
|
|
await Parse.Object.saveAll([user, user2, user3]);
|
|
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
ACL.setReadAccess(user2, true);
|
|
ACL.setWriteAccess(user2, true);
|
|
ACL.setReadAccess(user3, true);
|
|
ACL.setWriteAccess(user3, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owners', [user2, user3]);
|
|
await obj.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Allow public and owners read
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
find: { '*': true },
|
|
get: { '*': true },
|
|
readUserFields: ['owners'],
|
|
}
|
|
);
|
|
|
|
for (const owner of ['user2', 'user3']) {
|
|
await Parse.User.logIn(owner, 'password');
|
|
try {
|
|
const objectAgain = await obj.fetch();
|
|
expect(objectAgain.id).toBe(obj.id);
|
|
} catch (err) {
|
|
done.fail('Should not fail fetching');
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('tests CLP / Pointer Perms / ACL read (ACL locked)', async done => {
|
|
/*
|
|
tests:
|
|
CLP: find/get open ({"*": true})
|
|
PointerPerm: "owners" : read // proper owner
|
|
ACL: logged in user has not access
|
|
*/
|
|
const config = Config.get(Parse.applicationId);
|
|
const user = new Parse.User();
|
|
const user2 = new Parse.User();
|
|
const user3 = new Parse.User();
|
|
user.set({
|
|
username: 'user1',
|
|
password: 'password',
|
|
});
|
|
user2.set({
|
|
username: 'user2',
|
|
password: 'password',
|
|
});
|
|
user3.set({
|
|
username: 'user3',
|
|
password: 'password',
|
|
});
|
|
const obj = new Parse.Object('AnObject');
|
|
await Parse.Object.saveAll([user, user2, user3]);
|
|
|
|
const ACL = new Parse.ACL();
|
|
ACL.setReadAccess(user, true);
|
|
ACL.setWriteAccess(user, true);
|
|
obj.setACL(ACL);
|
|
obj.set('owners', [user2, user3]);
|
|
await obj.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Allow public and owners read
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{},
|
|
{
|
|
find: { '*': true },
|
|
get: { '*': true },
|
|
readUserFields: ['owners'],
|
|
}
|
|
);
|
|
|
|
for (const owner of ['user2', 'user3']) {
|
|
await Parse.User.logIn(owner, 'password');
|
|
try {
|
|
await obj.fetch();
|
|
done.fail('Should not succeed fetching');
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
}
|
|
}
|
|
done();
|
|
});
|
|
|
|
it('should let master key find objects', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
await object.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Lock the find/get, and let only owners read
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{ owners: { type: 'Array' } },
|
|
{ find: {}, get: {}, readUserFields: ['owners'] }
|
|
);
|
|
|
|
const q = new Parse.Query('AnObject');
|
|
const objects = await q.find();
|
|
expect(objects.length).toBe(0);
|
|
|
|
try {
|
|
const objects = await q.find({ useMasterKey: true });
|
|
expect(objects.length).toBe(1);
|
|
done();
|
|
} catch (err) {
|
|
done.fail('master key should find the object');
|
|
}
|
|
});
|
|
|
|
it('should let master key get objects', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
|
|
await object.save();
|
|
const schema = await config.database.loadSchema();
|
|
// Lock the find/get, and let only owners read
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{ owners: { type: 'Array' } },
|
|
{ find: {}, get: {}, readUserFields: ['owners'] }
|
|
);
|
|
|
|
const q = new Parse.Query('AnObject');
|
|
try {
|
|
await q.get(object.id);
|
|
done.fail();
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
}
|
|
|
|
try {
|
|
const objectAgain = await q.get(object.id, { useMasterKey: true });
|
|
expect(objectAgain).not.toBeUndefined();
|
|
expect(objectAgain.id).toBe(object.id);
|
|
done();
|
|
} catch (err) {
|
|
done.fail('master key should get the object');
|
|
}
|
|
});
|
|
|
|
it('should let master key update objects', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
await object.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Lock the update, and let only owners write
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{ owners: { type: 'Array' } },
|
|
{ update: {}, writeUserFields: ['owners'] }
|
|
);
|
|
|
|
try {
|
|
await object.save({ hello: 'bar' });
|
|
done.fail();
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
}
|
|
|
|
try {
|
|
const objectAgain = await object.save({ hello: 'baz' }, { useMasterKey: true });
|
|
expect(objectAgain.get('hello')).toBe('baz');
|
|
done();
|
|
} catch (err) {
|
|
done.fail('master key should save the object');
|
|
}
|
|
});
|
|
|
|
it('should let master key delete objects', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
|
|
const object = new Parse.Object('AnObject');
|
|
object.set('hello', 'world');
|
|
await object.save();
|
|
|
|
const schema = await config.database.loadSchema();
|
|
// Lock the delete, and let only owners write
|
|
await schema.updateClass(
|
|
'AnObject',
|
|
{ owners: { type: 'Array' } },
|
|
{ delete: {}, writeUserFields: ['owners'] }
|
|
);
|
|
|
|
try {
|
|
await object.destroy();
|
|
done.fail();
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.OBJECT_NOT_FOUND);
|
|
}
|
|
try {
|
|
await object.destroy({ useMasterKey: true });
|
|
done();
|
|
} catch (err) {
|
|
done.fail('master key should destroy the object');
|
|
}
|
|
});
|
|
|
|
it('should fail with invalid pointer perms (not array)', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const schema = await config.database.loadSchema();
|
|
try {
|
|
// Lock the delete, and let only owners write
|
|
await schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owners: { type: 'Array' } },
|
|
{ delete: {}, writeUserFields: 'owners' }
|
|
);
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
|
done();
|
|
}
|
|
});
|
|
|
|
it('should fail with invalid pointer perms (non-existing field)', async done => {
|
|
const config = Config.get(Parse.applicationId);
|
|
const schema = await config.database.loadSchema();
|
|
try {
|
|
// Lock the delete, and let only owners write
|
|
await schema.addClassIfNotExists(
|
|
'AnObject',
|
|
{ owners: { type: 'Array' } },
|
|
{ delete: {}, writeUserFields: ['owners', 'invalid'] }
|
|
);
|
|
} catch (err) {
|
|
expect(err.code).toBe(Parse.Error.INVALID_JSON);
|
|
done();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Granular ', () => {
|
|
const className = 'AnObject';
|
|
|
|
const actionGet = id => new Parse.Query(className).get(id);
|
|
const actionFind = () => new Parse.Query(className).find();
|
|
const actionCount = () => new Parse.Query(className).count();
|
|
const actionCreate = () => new Parse.Object(className).save();
|
|
const actionUpdate = obj => obj.save({ revision: 2 });
|
|
const actionDelete = obj => obj.destroy();
|
|
const actionAddFieldOnCreate = () =>
|
|
new Parse.Object(className, { ['extra' + Date.now()]: 'field' }).save();
|
|
const actionAddFieldOnUpdate = obj => obj.save({ ['another' + Date.now()]: 'field' });
|
|
|
|
const OBJECT_NOT_FOUND = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, 'Object not found.');
|
|
const PERMISSION_DENIED = jasmine.stringMatching('Permission denied');
|
|
|
|
async function createUser(username, password = 'password') {
|
|
const user = new Parse.User({
|
|
username: username + Date.now(),
|
|
password,
|
|
});
|
|
|
|
await user.save();
|
|
|
|
return user;
|
|
}
|
|
|
|
async function logIn(userObject) {
|
|
return await Parse.User.logIn(userObject.getUsername(), 'password');
|
|
}
|
|
|
|
async function updateCLP(clp) {
|
|
const config = Config.get(Parse.applicationId);
|
|
const schemaController = await config.database.loadSchema();
|
|
|
|
await schemaController.updateClass(className, {}, clp);
|
|
}
|
|
|
|
describe('on single-pointer fields', () => {
|
|
/** owns: **obj1** */
|
|
let user1;
|
|
|
|
/** owns: **obj2** */
|
|
let user2;
|
|
|
|
/** owned by: **user1** */
|
|
let obj1;
|
|
|
|
/** owned by: **user2** */
|
|
let obj2;
|
|
|
|
async function initialize() {
|
|
await Config.get(Parse.applicationId).schemaCache.clear();
|
|
|
|
[user1, user2] = await Promise.all([createUser('user1'), createUser('user2')]);
|
|
|
|
obj1 = new Parse.Object(className, {
|
|
owner: user1,
|
|
revision: 0,
|
|
});
|
|
|
|
obj2 = new Parse.Object(className, {
|
|
owner: user2,
|
|
revision: 0,
|
|
});
|
|
|
|
await Parse.Object.saveAll([obj1, obj2], {
|
|
useMasterKey: true,
|
|
});
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await initialize();
|
|
});
|
|
|
|
describe('get action', () => {
|
|
it('should be allowed', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const result = await actionGet(obj1.id);
|
|
expect(result).toBeDefined();
|
|
done();
|
|
});
|
|
|
|
it_id('9ba681d5-59f5-4996-b36d-6647d23e6a44')(it)('should fail for user not listed', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionGet(obj1.id)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionFind(),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionUpdate(obj1),
|
|
actionAddFieldOnCreate(),
|
|
actionDelete(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('find action', () => {
|
|
it('should be allowed', async done => {
|
|
await updateCLP({
|
|
find: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionFind()).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it('should be limited to objects where user is listed in field', async done => {
|
|
await updateCLP({
|
|
find: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
const results = await actionFind();
|
|
expect(results.length).toBe(1);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
find: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionUpdate(obj1),
|
|
actionAddFieldOnCreate(),
|
|
actionDelete(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('count action', () => {
|
|
it('should be allowed', async done => {
|
|
await updateCLP({
|
|
count: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const count = await actionCount();
|
|
expect(count).toBe(1);
|
|
done();
|
|
});
|
|
|
|
it('should be limited to objects where user is listed in field', async done => {
|
|
await updateCLP({
|
|
count: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
const user3 = await createUser('user3');
|
|
await logIn(user3);
|
|
|
|
const p = await actionCount();
|
|
expect(p).toBe(0);
|
|
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
count: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionFind(),
|
|
actionCreate(),
|
|
actionUpdate(obj1),
|
|
actionAddFieldOnCreate(),
|
|
actionDelete(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('update action', () => {
|
|
it('should be allowed', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
await expectAsync(actionUpdate(obj1)).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it_id('bcdb158d-c0b6-45e3-84ab-a3636f7cb470')(it)('should fail for user not listed', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionUpdate(obj1)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionFind(),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionAddFieldOnCreate(),
|
|
actionDelete(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('delete action', () => {
|
|
it('should be allowed', async done => {
|
|
await updateCLP({
|
|
delete: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionDelete(obj1)).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it_id('70aa3853-6e26-4c38-a927-2ddb24ced7d4')(it)('should fail for user not listed', async done => {
|
|
await updateCLP({
|
|
delete: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionDelete(obj1)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
delete: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionFind(),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionUpdate(obj1),
|
|
actionAddFieldOnCreate(),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('create action', () => {
|
|
// For Pointer permissions create is different from other operations
|
|
// since there's no object holding the pointer before created
|
|
it('should be denied (writelock) when no other permissions on class', async done => {
|
|
await updateCLP({
|
|
create: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
await expectAsync(actionCreate()).toBeRejectedWith(PERMISSION_DENIED);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('addField action', () => {
|
|
xit('should have no effect when creating object (and allowed by explicit userid permission)', async done => {
|
|
await updateCLP({
|
|
create: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
[user1.id]: true,
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionAddFieldOnCreate()).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
xit('should be denied when creating object (and no explicit permission)', async done => {
|
|
await updateCLP({
|
|
create: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const newObject = new Parse.Object(className, {
|
|
owner: user1,
|
|
extra: 'field',
|
|
});
|
|
await expectAsync(newObject.save()).toBeRejectedWith(PERMISSION_DENIED);
|
|
done();
|
|
});
|
|
|
|
it('should be allowed when updating object', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionAddFieldOnUpdate(obj1)).toBeResolved();
|
|
|
|
done();
|
|
});
|
|
|
|
it('should be denied when updating object for user without addField permission', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionAddFieldOnUpdate(obj1)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('on array of pointers', () => {
|
|
/**
|
|
* owns: **obj1**
|
|
*
|
|
* moderates: **obj1** */
|
|
let user1;
|
|
|
|
/**
|
|
* owns: **obj2**
|
|
*
|
|
* moderates: **obj1, obj2** */
|
|
let user2;
|
|
|
|
/**
|
|
* owns: **obj3**
|
|
*
|
|
* moderates: **obj1, obj2, obj3 ** */
|
|
let user3;
|
|
|
|
/**
|
|
* owned by: **user1**
|
|
*
|
|
* moderated by: **user1, user2, user3** */
|
|
let obj1;
|
|
|
|
/**
|
|
* owned by: **user2**
|
|
*
|
|
* moderated by: **user2, user3** */
|
|
let obj2;
|
|
|
|
/**
|
|
* owned by: **user3**
|
|
*
|
|
* moderated by: **user3** */
|
|
let obj3;
|
|
|
|
/**
|
|
* owned by: **noboody**
|
|
*
|
|
* moderated by: **nobody** */
|
|
let objNobody;
|
|
|
|
async function initialize() {
|
|
await Config.get(Parse.applicationId).schemaCache.clear();
|
|
|
|
[user1, user2, user3] = await Promise.all([
|
|
createUser('user1'),
|
|
createUser('user2'),
|
|
createUser('user3'),
|
|
]);
|
|
|
|
obj1 = new Parse.Object(className);
|
|
obj2 = new Parse.Object(className);
|
|
obj3 = new Parse.Object(className);
|
|
objNobody = new Parse.Object(className);
|
|
|
|
obj1.set({
|
|
owners: [user1],
|
|
moderators: [user3, user2, user1],
|
|
revision: 0,
|
|
});
|
|
|
|
obj2.set({
|
|
owners: [user2],
|
|
moderators: [user3, user2],
|
|
revision: 0,
|
|
});
|
|
|
|
obj3.set({
|
|
owners: [user3],
|
|
moderators: [user3],
|
|
revision: 0,
|
|
});
|
|
|
|
objNobody.set({
|
|
owners: [],
|
|
moderators: [],
|
|
revision: 0,
|
|
});
|
|
|
|
await Parse.Object.saveAll([obj1, obj2, obj3, objNobody], {
|
|
useMasterKey: true,
|
|
});
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await initialize();
|
|
});
|
|
|
|
describe('get action', () => {
|
|
it('should be allowed (1 user in array)', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['owners'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const result = await actionGet(obj1.id);
|
|
expect(result).toBeDefined();
|
|
done();
|
|
});
|
|
|
|
it('should be allowed (multiple users in array)', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
const result = await actionGet(obj1.id);
|
|
expect(result).toBeDefined();
|
|
done();
|
|
});
|
|
|
|
it_id('84a42339-c7b5-4735-a431-57b46535b073')(it)('should fail for user not listed', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionGet(obj3.id)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['owners'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionFind(),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionUpdate(obj2),
|
|
actionAddFieldOnCreate(),
|
|
actionAddFieldOnUpdate(obj2),
|
|
actionDelete(obj2),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('find action', () => {
|
|
it('should be allowed (1 user in array)', async done => {
|
|
await updateCLP({
|
|
find: {
|
|
pointerFields: ['owners'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const results = await actionFind();
|
|
expect(results.length).toBe(1);
|
|
done();
|
|
});
|
|
|
|
it('should be allowed (multiple users in array)', async done => {
|
|
await updateCLP({
|
|
find: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
const results = await actionFind();
|
|
expect(results.length).toBe(2);
|
|
done();
|
|
});
|
|
|
|
it('should be limited to objects where user is listed in field', async done => {
|
|
await updateCLP({
|
|
find: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const results = await actionFind();
|
|
expect(results.length).toBe(1);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
find: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionUpdate(obj1),
|
|
actionAddFieldOnCreate(),
|
|
actionAddFieldOnUpdate(obj1),
|
|
actionDelete(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('count action', () => {
|
|
beforeEach(async () => {
|
|
await updateCLP({
|
|
count: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
});
|
|
|
|
it('should be allowed', async done => {
|
|
await logIn(user1);
|
|
|
|
const count = await actionCount();
|
|
expect(count).toBe(1);
|
|
done();
|
|
});
|
|
|
|
it('should be limited to objects where user is listed in field', async done => {
|
|
await logIn(user2);
|
|
|
|
const count = await actionCount();
|
|
expect(count).toBe(2);
|
|
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionFind(),
|
|
actionCreate(),
|
|
actionUpdate(obj1),
|
|
actionAddFieldOnCreate(),
|
|
actionAddFieldOnUpdate(obj1),
|
|
actionDelete(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('update action', () => {
|
|
it('should be allowed (1 user in array)', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['owners'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionUpdate(obj1)).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it_id('2b19234a-a471-48b4-bd1a-27bd286d066f')(it)('should be allowed (multiple users in array)', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionUpdate(obj1)).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it_id('1abb9f4a-fb24-48c7-8025-3001d6cf8737')(it)('should fail for user not listed', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionUpdate(obj3)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionFind(),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionAddFieldOnCreate(),
|
|
actionAddFieldOnUpdate(obj1),
|
|
actionDelete(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('delete action', () => {
|
|
it('should be allowed (1 user in array)', async done => {
|
|
await updateCLP({
|
|
delete: {
|
|
pointerFields: ['owners'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionDelete(obj1)).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it('should be allowed (multiple users in array)', async done => {
|
|
await updateCLP({
|
|
delete: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user3);
|
|
|
|
await expectAsync(actionDelete(obj2)).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it_id('3175a0e3-e51e-4b84-a2e6-50bbcc582123')(it)('should fail for user not listed', async done => {
|
|
await updateCLP({
|
|
delete: {
|
|
pointerFields: ['owners'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionDelete(obj3)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
done();
|
|
});
|
|
|
|
it('should not allow other actions', async done => {
|
|
await updateCLP({
|
|
delete: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await Promise.all(
|
|
[
|
|
actionGet(obj1.id),
|
|
actionFind(),
|
|
actionCount(),
|
|
actionCreate(),
|
|
actionUpdate(obj1),
|
|
actionAddFieldOnCreate(),
|
|
actionAddFieldOnUpdate(obj1),
|
|
].map(async p => {
|
|
await expectAsync(p).toBeRejectedWith(PERMISSION_DENIED);
|
|
})
|
|
);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('create action', () => {
|
|
/* For Pointer permissions 'create' is different from other operations
|
|
since there's no object holding the pointer before created */
|
|
it('should be denied (writelock) when no other permissions on class', async done => {
|
|
await updateCLP({
|
|
create: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
await expectAsync(actionCreate()).toBeRejectedWith(PERMISSION_DENIED);
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('addField action', () => {
|
|
it('should have no effect on create (allowed by explicit userid)', async done => {
|
|
await updateCLP({
|
|
create: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
[user1.id]: true,
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionAddFieldOnCreate()).toBeResolved();
|
|
done();
|
|
});
|
|
|
|
it('should be denied when creating object (and no explicit permission)', async done => {
|
|
await updateCLP({
|
|
create: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const newObject = new Parse.Object(className, {
|
|
moderators: user1,
|
|
extra: 'field',
|
|
});
|
|
await expectAsync(newObject.save()).toBeRejectedWith(PERMISSION_DENIED);
|
|
done();
|
|
});
|
|
|
|
it('should be allowed when updating object', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionAddFieldOnUpdate(obj1)).toBeResolved();
|
|
|
|
done();
|
|
});
|
|
|
|
it_id('51e896e9-73b3-404f-b5ff-bdb99005a9f7')(it)('should be restricted when updating object without addField permission', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
'*': true,
|
|
},
|
|
addField: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionAddFieldOnUpdate(obj2)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('combined with grouped', () => {
|
|
/**
|
|
* owns: **obj1**
|
|
*
|
|
* moderates: **obj2** */
|
|
let user1;
|
|
|
|
/**
|
|
* owns: **obj2**
|
|
*
|
|
* moderates: **obj1, obj2** */
|
|
let user2;
|
|
|
|
/**
|
|
* owned by: **user1**
|
|
*
|
|
* moderated by: **user2** */
|
|
let obj1;
|
|
|
|
/**
|
|
* owned by: **user2**
|
|
*
|
|
* moderated by: **user1, user2** */
|
|
let obj2;
|
|
|
|
async function initialize() {
|
|
await Config.get(Parse.applicationId).schemaCache.clear();
|
|
|
|
[user1, user2] = await Promise.all([createUser('user1'), createUser('user2')]);
|
|
|
|
// User1 owns object1
|
|
// User2 owns object2
|
|
obj1 = new Parse.Object(className, {
|
|
owner: user1,
|
|
moderators: [user2],
|
|
revision: 0,
|
|
});
|
|
|
|
obj2 = new Parse.Object(className, {
|
|
owner: user2,
|
|
moderators: [user1, user2],
|
|
revision: 0,
|
|
});
|
|
|
|
await Parse.Object.saveAll([obj1, obj2], {
|
|
useMasterKey: true,
|
|
});
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await initialize();
|
|
});
|
|
|
|
it_id('b43db366-8cce-4a11-9cf2-eeee9603d40b')(it)('should not limit the scope of grouped read permissions', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
readUserFields: ['moderators'],
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionGet(obj1.id)).toBeResolved();
|
|
|
|
const found = await actionFind();
|
|
expect(found.length).toBe(2);
|
|
|
|
const counted = await actionCount();
|
|
expect(counted).toBe(2);
|
|
|
|
done();
|
|
});
|
|
|
|
it_id('bbb1686d-0e2a-4365-8b64-b5faa3e7b9cf')(it)('should not limit the scope of grouped write permissions', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
writeUserFields: ['moderators'],
|
|
});
|
|
|
|
await logIn(user2);
|
|
|
|
await expectAsync(actionUpdate(obj1)).toBeResolved();
|
|
await expectAsync(actionAddFieldOnUpdate(obj1)).toBeResolved();
|
|
await expectAsync(actionDelete(obj1)).toBeResolved();
|
|
// [create] and [addField on create] can't be enabled with pointer by design
|
|
|
|
done();
|
|
});
|
|
|
|
it('should not inherit scope of grouped read permissions from another field', async done => {
|
|
await updateCLP({
|
|
get: {
|
|
pointerFields: ['owner'],
|
|
},
|
|
readUserFields: ['moderators'],
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
const found = await actionFind();
|
|
expect(found.length).toBe(1);
|
|
|
|
const counted = await actionCount();
|
|
expect(counted).toBe(1);
|
|
|
|
done();
|
|
});
|
|
|
|
it('should not inherit scope of grouped write permissions from another field', async done => {
|
|
await updateCLP({
|
|
update: {
|
|
pointerFields: ['moderators'],
|
|
},
|
|
writeUserFields: ['owner'],
|
|
});
|
|
|
|
await logIn(user1);
|
|
|
|
await expectAsync(actionDelete(obj2)).toBeRejectedWith(OBJECT_NOT_FOUND);
|
|
|
|
done();
|
|
});
|
|
});
|
|
|
|
describe('using pointer-fields and queries with keys projection', () => {
|
|
let user1;
|
|
/**
|
|
* owner: user1
|
|
*
|
|
* testers: [user1]
|
|
*/
|
|
let obj;
|
|
|
|
/**
|
|
* Clear cache, create user and object, login user
|
|
*/
|
|
async function initialize() {
|
|
await Config.get(Parse.applicationId).schemaCache.clear();
|
|
|
|
user1 = await createUser('user1');
|
|
user1 = await logIn(user1);
|
|
|
|
obj = new Parse.Object(className);
|
|
|
|
obj.set('owner', user1);
|
|
obj.set('field', 'field');
|
|
obj.set('test', 'test');
|
|
|
|
await Parse.Object.saveAll([obj], { useMasterKey: true });
|
|
|
|
await obj.fetch();
|
|
}
|
|
|
|
beforeEach(async () => {
|
|
await initialize();
|
|
});
|
|
|
|
it('should be enforced regardless of pointer-field being included in keys (select)', async done => {
|
|
await updateCLP({
|
|
get: { '*': true },
|
|
find: { pointerFields: ['owner'] },
|
|
update: { pointerFields: ['owner'] },
|
|
});
|
|
|
|
const query = new Parse.Query('AnObject');
|
|
query.select('field', 'test');
|
|
|
|
const [object] = await query.find({ objectId: obj.id });
|
|
expect(object.get('field')).toBe('field');
|
|
expect(object.get('test')).toBe('test');
|
|
done();
|
|
});
|
|
});
|
|
});
|
|
});
|