Protected fields pointer-permissions support (#5951)
* moved whitelisting of own user to remove conflict with custom classes and * permission * added new pointer-perm regex to permissions * added pointer-permissions support * added tests * fixed typo * fixed typo 2 * added tests using find operation * renamed protectedFields pointerPerm to userField * decoupled readUserFields from CLP and removed readUser from protectedFields before querying * updated tests
This commit is contained in:
committed by
Antonio Davi Macedo Coelho de Castro
parent
6ed0a2289a
commit
2b1c591cb7
@@ -1,3 +1,6 @@
|
|||||||
|
const Config = require('../lib/Config');
|
||||||
|
const Parse = require('parse/node');
|
||||||
|
|
||||||
describe('ProtectedFields', function() {
|
describe('ProtectedFields', function() {
|
||||||
it('should handle and empty protectedFields', async function() {
|
it('should handle and empty protectedFields', async function() {
|
||||||
const protectedFields = {};
|
const protectedFields = {};
|
||||||
@@ -138,4 +141,621 @@ describe('ProtectedFields', function() {
|
|||||||
expect(fetchedUser.has('favoriteColor')).toBeTruthy();
|
expect(fetchedUser.has('favoriteColor')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('using the pointer-permission variant', () => {
|
||||||
|
let user1, user2;
|
||||||
|
beforeEach(async () => {
|
||||||
|
Config.get(Parse.applicationId).database.schemaCache.clear();
|
||||||
|
user1 = await Parse.User.signUp('user1', 'password');
|
||||||
|
user2 = await Parse.User.signUp('user2', 'password');
|
||||||
|
await Parse.User.logOut();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and get/fetch', () => {
|
||||||
|
it('should allow access using single user pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owner'], 'userField:owner': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
const objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owner').id).toBe(user1.id);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to other users using single user pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owner'], 'userField:owner': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user2', 'password');
|
||||||
|
const objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owner')).toBe(undefined);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to public using single user pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owner'], 'userField:owner': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owner')).toBe(undefined);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access using user array pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1, user2]);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owners'], 'userField:owners': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
let objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owners')[0].id).toBe(user1.id);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
await Parse.User.logIn('user2', 'password');
|
||||||
|
objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owners')[1].id).toBe(user2.id);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to other users using user array pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1]);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owners'], 'userField:owners': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user2', 'password');
|
||||||
|
const objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owners')).toBe(undefined);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to public using user array pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1, user2]);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owners'], 'userField:owners': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owners')).toBe(undefined);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create merge protected fields when using multiple pointer-permission fields', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1]);
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: {
|
||||||
|
'*': [],
|
||||||
|
'userField:owners': ['owners'],
|
||||||
|
'userField:owner': ['owner'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if protectFields from pointer-permissions got combined
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
const objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owners')).toBe(undefined);
|
||||||
|
expect(objectAgain.get('owner')).toBe(undefined);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore pointer-permission fields not present in object', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1]);
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
await obj.save();
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: {
|
||||||
|
'*': [],
|
||||||
|
'userField:idontexist': ['owner'],
|
||||||
|
'userField:idontexist2': ['owners'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
const objectAgain = await obj.fetch();
|
||||||
|
expect(objectAgain.get('owners')).not.toBe(undefined);
|
||||||
|
expect(objectAgain.get('owner')).not.toBe(undefined);
|
||||||
|
expect(objectAgain.get('test')).toBe('test');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('and find', () => {
|
||||||
|
it('should allow access using single user pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owner', user1);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owner'], 'userField:owner': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
const results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owner').id).toBe(user1.id);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owner').id).toBe(user1.id);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to other users using single user pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owner', user1);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owner'], 'userField:owner': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user2', 'password');
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
const results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owner')).toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owner')).toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to public using single user pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owner', user1);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owner'], 'userField:owner': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
const results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owner')).toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owner')).toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access using user array pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1, user2]);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owners', [user1, user2]);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owners'], 'userField:owners': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
let results;
|
||||||
|
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owners')[0].id).toBe(user1.id);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owners')[0].id).toBe(user1.id);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
|
||||||
|
await Parse.User.logIn('user2', 'password');
|
||||||
|
results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owners')[1].id).toBe(user2.id);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owners')[1].id).toBe(user2.id);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to other users using user array pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1]);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owners', [user1]);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owners'], 'userField:owners': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user2', 'password');
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
const results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owners')).toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owners')).toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should deny access to public using user array pointer-permissions', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1, user2]);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owners', [user1, user2]);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: { '*': ['owners'], 'userField:owners': [] },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
const results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owners')).toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owners')).toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create merge protected fields when using multiple pointer-permission fields', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1]);
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owners', [user1]);
|
||||||
|
obj2.set('owner', user1);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: {
|
||||||
|
'*': [],
|
||||||
|
'userField:owners': ['owners'],
|
||||||
|
'userField:owner': ['owner'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check if protectFields from pointer-permissions got combined
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
const results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owners')).toBe(undefined);
|
||||||
|
expect(results[0].get('owner')).toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owners')).toBe(undefined);
|
||||||
|
expect(results[1].get('owner')).toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should ignore pointer-permission fields not present in object', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owners', [user1]);
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owners', [user1]);
|
||||||
|
obj2.set('owner', user1);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
await Parse.Object.saveAll([obj, obj2]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: {
|
||||||
|
'*': [],
|
||||||
|
'userField:idontexist': ['owner'],
|
||||||
|
'userField:idontexist2': ['owners'],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
const results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(2);
|
||||||
|
|
||||||
|
expect(results[0].get('owners')).not.toBe(undefined);
|
||||||
|
expect(results[0].get('owner')).not.toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owners')).not.toBe(undefined);
|
||||||
|
expect(results[1].get('owner')).not.toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should filter only fields from objects not owned by the user', async done => {
|
||||||
|
const config = Config.get(Parse.applicationId);
|
||||||
|
const obj = new Parse.Object('AnObject');
|
||||||
|
const obj2 = new Parse.Object('AnObject');
|
||||||
|
const obj3 = new Parse.Object('AnObject');
|
||||||
|
|
||||||
|
obj.set('owner', user1);
|
||||||
|
obj.set('test', 'test');
|
||||||
|
obj2.set('owner', user2);
|
||||||
|
obj2.set('test', 'test2');
|
||||||
|
obj3.set('owner', user2);
|
||||||
|
obj3.set('test', 'test3');
|
||||||
|
await Parse.Object.saveAll([obj, obj2, obj3]);
|
||||||
|
|
||||||
|
const schema = await config.database.loadSchema();
|
||||||
|
await schema.updateClass(
|
||||||
|
'AnObject',
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: { '*': true },
|
||||||
|
find: { '*': true },
|
||||||
|
protectedFields: {
|
||||||
|
'*': ['owner'],
|
||||||
|
'userField:owner': [],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const q = new Parse.Query('AnObject');
|
||||||
|
let results;
|
||||||
|
|
||||||
|
await Parse.User.logIn('user1', 'password');
|
||||||
|
|
||||||
|
results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(3);
|
||||||
|
|
||||||
|
expect(results[0].get('owner')).not.toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owner')).toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
expect(results[2].get('owner')).toBe(undefined);
|
||||||
|
expect(results[2].get('test')).toBe('test3');
|
||||||
|
|
||||||
|
await Parse.User.logIn('user2', 'password');
|
||||||
|
|
||||||
|
results = await q.find();
|
||||||
|
// sort for checking in correct order
|
||||||
|
results.sort((a, b) => a.get('test').localeCompare(b.get('test')));
|
||||||
|
expect(results.length).toBe(3);
|
||||||
|
|
||||||
|
expect(results[0].get('owner')).toBe(undefined);
|
||||||
|
expect(results[0].get('test')).toBe('test');
|
||||||
|
expect(results[1].get('owner')).not.toBe(undefined);
|
||||||
|
expect(results[1].get('test')).toBe('test2');
|
||||||
|
expect(results[2].get('owner')).not.toBe(undefined);
|
||||||
|
expect(results[2].get('test')).toBe('test3');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -192,15 +192,69 @@ const validateQuery = (
|
|||||||
|
|
||||||
// Filters out any data that shouldn't be on this REST-formatted object.
|
// Filters out any data that shouldn't be on this REST-formatted object.
|
||||||
const filterSensitiveData = (
|
const filterSensitiveData = (
|
||||||
isMaster,
|
isMaster: boolean,
|
||||||
aclGroup,
|
aclGroup: any[],
|
||||||
className,
|
auth: any,
|
||||||
protectedFields,
|
operation: any,
|
||||||
object
|
schema: SchemaController.SchemaController,
|
||||||
|
className: string,
|
||||||
|
protectedFields: null | Array<any>,
|
||||||
|
object: any
|
||||||
) => {
|
) => {
|
||||||
protectedFields && protectedFields.forEach(k => delete object[k]);
|
let userId = null;
|
||||||
|
if (auth && auth.user) userId = auth.user.id;
|
||||||
|
|
||||||
if (className !== '_User') {
|
// replace protectedFields when using pointer-permissions
|
||||||
|
const perms = schema.getClassLevelPermissions(className);
|
||||||
|
if (perms) {
|
||||||
|
const isReadOperation = ['get', 'find'].indexOf(operation) > -1;
|
||||||
|
|
||||||
|
if (isReadOperation && perms.protectedFields) {
|
||||||
|
// extract protectedFields added with the pointer-permission prefix
|
||||||
|
const protectedFieldsPointerPerm = Object.keys(perms.protectedFields)
|
||||||
|
.filter(key => key.startsWith('userField:'))
|
||||||
|
.map(key => {
|
||||||
|
return { key: key.substring(10), value: perms.protectedFields[key] };
|
||||||
|
});
|
||||||
|
|
||||||
|
const newProtectedFields: Array<string> = [];
|
||||||
|
let overrideProtectedFields = false;
|
||||||
|
|
||||||
|
// check if the object grants the current user access based on the extracted fields
|
||||||
|
protectedFieldsPointerPerm.forEach(pointerPerm => {
|
||||||
|
let pointerPermIncludesUser = false;
|
||||||
|
const readUserFieldValue = object[pointerPerm.key];
|
||||||
|
if (readUserFieldValue) {
|
||||||
|
if (Array.isArray(readUserFieldValue)) {
|
||||||
|
pointerPermIncludesUser = readUserFieldValue.some(
|
||||||
|
user => user.objectId && user.objectId === userId
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
pointerPermIncludesUser =
|
||||||
|
readUserFieldValue.objectId &&
|
||||||
|
readUserFieldValue.objectId === userId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pointerPermIncludesUser) {
|
||||||
|
overrideProtectedFields = true;
|
||||||
|
newProtectedFields.push(...pointerPerm.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// if atleast one pointer-permission affected the current user override the protectedFields
|
||||||
|
if (overrideProtectedFields) protectedFields = newProtectedFields;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const isUserClass = className === '_User';
|
||||||
|
|
||||||
|
/* special treat for the user class: don't filter protectedFields if currently loggedin user is
|
||||||
|
the retrieved user */
|
||||||
|
if (!(isUserClass && userId && object.objectId === userId))
|
||||||
|
protectedFields && protectedFields.forEach(k => delete object[k]);
|
||||||
|
|
||||||
|
if (!isUserClass) {
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1315,8 +1369,9 @@ class DatabaseController {
|
|||||||
query,
|
query,
|
||||||
aclGroup
|
aclGroup
|
||||||
);
|
);
|
||||||
// ProtectedFields is generated before executing the query so we
|
/* Don't use projections to optimize the protectedFields since the protectedFields
|
||||||
// can optimize the query using Mongo Projection at a later stage.
|
based on pointer-permissions are determined after querying. The filtering can
|
||||||
|
overwrite the protected fields. */
|
||||||
protectedFields = this.addProtectedFields(
|
protectedFields = this.addProtectedFields(
|
||||||
schemaController,
|
schemaController,
|
||||||
className,
|
className,
|
||||||
@@ -1385,6 +1440,9 @@ class DatabaseController {
|
|||||||
return filterSensitiveData(
|
return filterSensitiveData(
|
||||||
isMaster,
|
isMaster,
|
||||||
aclGroup,
|
aclGroup,
|
||||||
|
auth,
|
||||||
|
op,
|
||||||
|
schemaController,
|
||||||
className,
|
className,
|
||||||
protectedFields,
|
protectedFields,
|
||||||
object
|
object
|
||||||
@@ -1518,18 +1576,13 @@ class DatabaseController {
|
|||||||
if (!protectedFields) return null;
|
if (!protectedFields) return null;
|
||||||
|
|
||||||
if (aclGroup.indexOf(query.objectId) > -1) return null;
|
if (aclGroup.indexOf(query.objectId) > -1) return null;
|
||||||
if (
|
|
||||||
Object.keys(query).length === 0 &&
|
|
||||||
auth &&
|
|
||||||
auth.user &&
|
|
||||||
aclGroup.indexOf(auth.user.id) > -1
|
|
||||||
)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
let protectedKeys = Object.values(protectedFields).reduce(
|
// remove userField keys since they are filtered after querying
|
||||||
(acc, val) => acc.concat(val),
|
let protectedKeys = Object.keys(protectedFields).reduce((acc, val) => {
|
||||||
[]
|
if (val.startsWith('userField:')) return acc;
|
||||||
); //.flat();
|
return acc.concat(protectedFields[val]);
|
||||||
|
}, []);
|
||||||
|
|
||||||
[...(auth.userRoles || [])].forEach(role => {
|
[...(auth.userRoles || [])].forEach(role => {
|
||||||
const fields = protectedFields[role];
|
const fields = protectedFields[role];
|
||||||
if (fields) {
|
if (fields) {
|
||||||
|
|||||||
@@ -177,6 +177,8 @@ const volatileClasses = Object.freeze([
|
|||||||
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
const userIdRegex = /^[a-zA-Z0-9]{10}$/;
|
||||||
// Anything that start with role
|
// Anything that start with role
|
||||||
const roleRegex = /^role:.*/;
|
const roleRegex = /^role:.*/;
|
||||||
|
// Anything that starts with userField
|
||||||
|
const pointerPermissionRegex = /^userField:.*/;
|
||||||
// * permission
|
// * permission
|
||||||
const publicRegex = /^\*$/;
|
const publicRegex = /^\*$/;
|
||||||
|
|
||||||
@@ -185,6 +187,7 @@ const requireAuthenticationRegex = /^requiresAuthentication$/;
|
|||||||
const permissionKeyRegex = Object.freeze([
|
const permissionKeyRegex = Object.freeze([
|
||||||
userIdRegex,
|
userIdRegex,
|
||||||
roleRegex,
|
roleRegex,
|
||||||
|
pointerPermissionRegex,
|
||||||
publicRegex,
|
publicRegex,
|
||||||
requireAuthenticationRegex,
|
requireAuthenticationRegex,
|
||||||
]);
|
]);
|
||||||
@@ -906,10 +909,15 @@ export default class SchemaController {
|
|||||||
let defaultValueType = getType(fieldType.defaultValue);
|
let defaultValueType = getType(fieldType.defaultValue);
|
||||||
if (typeof defaultValueType === 'string') {
|
if (typeof defaultValueType === 'string') {
|
||||||
defaultValueType = { type: defaultValueType };
|
defaultValueType = { type: defaultValueType };
|
||||||
} else if (typeof defaultValueType === 'object' && fieldType.type === 'Relation') {
|
} else if (
|
||||||
|
typeof defaultValueType === 'object' &&
|
||||||
|
fieldType.type === 'Relation'
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
code: Parse.Error.INCORRECT_TYPE,
|
code: Parse.Error.INCORRECT_TYPE,
|
||||||
error: `The 'default value' option is not applicable for ${typeToString(fieldType)}`
|
error: `The 'default value' option is not applicable for ${typeToString(
|
||||||
|
fieldType
|
||||||
|
)}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (!dbTypeMatchesObjectType(fieldType, defaultValueType)) {
|
if (!dbTypeMatchesObjectType(fieldType, defaultValueType)) {
|
||||||
@@ -924,7 +932,9 @@ export default class SchemaController {
|
|||||||
if (typeof fieldType === 'object' && fieldType.type === 'Relation') {
|
if (typeof fieldType === 'object' && fieldType.type === 'Relation') {
|
||||||
return {
|
return {
|
||||||
code: Parse.Error.INCORRECT_TYPE,
|
code: Parse.Error.INCORRECT_TYPE,
|
||||||
error: `The 'required' option is not applicable for ${typeToString(fieldType)}`
|
error: `The 'required' option is not applicable for ${typeToString(
|
||||||
|
fieldType
|
||||||
|
)}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user