Skip to content

Commit

Permalink
Adds tests, improve coverage, adds ability to delete CLP with classLe…
Browse files Browse the repository at this point in the history
…velPermissions: null
  • Loading branch information
flovilmart committed Mar 10, 2016
1 parent ddd1ae3 commit 5f300ca
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 18 deletions.
278 changes: 278 additions & 0 deletions spec/schemas.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1122,4 +1122,282 @@ describe('schemas', () => {
})
});

function setPermissionsOnClass(className, permissions, doPut) {
let op = request.post;
if (doPut)
{
op = request.put;
}
return new Promise((resolve, reject) => {
op({
url: 'http://localhost:8378/1/schemas/'+className,
headers: masterKeyHeaders,
json: true,
body: {
classLevelPermissions: permissions
}
}, (error, response, body) => {
if (error) {
return reject(error);
}
if (body.error) {
return reject(body);
}
return resolve(body);
})
});
}

it('validate CLP 1', done => {
let user = new Parse.User();
user.setUsername('user');
user.setPassword('user');

let admin = new Parse.User();
admin.setUsername('admin');
admin.setPassword('admin');

let role = new Parse.Role('admin', new Parse.ACL());

setPermissionsOnClass('AClass', {
'find': {
'role:admin': true
}
}).then(() => {
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
}).then(()=> {
role.relation('users').add(admin);
return role.save(null, {useMasterKey: true});
}).then(() => {
return Parse.User.logIn('user', 'user').then(() => {
let obj = new Parse.Object('AClass');
return obj.save();
})
}).then(() => {
let query = new Parse.Query('AClass');
return query.find().then((err) => {
expect(err.message).toEqual('Permission denied for this action.');
fail('Use should hot be able to find!')
}, (err) => {
return Promise.resolve();
})
}).then(() => {
return Parse.User.logIn('admin', 'admin');
}).then( () => {
let query = new Parse.Query('AClass');
return query.find();
}).then((results) => {
expect(results.length).toBe(1);
done();
}, () => {
fail("should not fail!");
done();
}).catch( (err) => {
console.error(err);
done();
})
});

it('validate CLP 2', done => {
let user = new Parse.User();
user.setUsername('user');
user.setPassword('user');

let admin = new Parse.User();
admin.setUsername('admin');
admin.setPassword('admin');

let role = new Parse.Role('admin', new Parse.ACL());

setPermissionsOnClass('AClass', {
'find': {
'role:admin': true
}
}).then(() => {
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
}).then(()=> {
role.relation('users').add(admin);
return role.save(null, {useMasterKey: true});
}).then(() => {
return Parse.User.logIn('user', 'user').then(() => {
let obj = new Parse.Object('AClass');
return obj.save();
})
}).then(() => {
let query = new Parse.Query('AClass');
return query.find().then((err) => {
expect(err.message).toEqual('Permission denied for this action.');
fail('User should not be able to find!')
}, (err) => {
return Promise.resolve();
})
}).then(() => {
// let everyone see it now
return setPermissionsOnClass('AClass', {
'find': {
'role:admin': true,
'*': true
}
}, true);
}).then(() => {
let query = new Parse.Query('AClass');
return query.find().then((result) => {
expect(result.length).toBe(1);
}, (err) => {
console.error(err);
expect(err.message).toEqual('Permission denied for this action.');
fail('User should be able to find!')
done();
});
}).then(() => {
return Parse.User.logIn('admin', 'admin');
}).then( () => {
let query = new Parse.Query('AClass');
return query.find();
}).then((results) => {
expect(results.length).toBe(1);
done();
}, (err) => {
console.error(err);
fail("should not fail!");
done();
}).catch( (err) => {
console.error(err);
done();
})
});

it('validate CLP 3', done => {
let user = new Parse.User();
user.setUsername('user');
user.setPassword('user');

let admin = new Parse.User();
admin.setUsername('admin');
admin.setPassword('admin');

let role = new Parse.Role('admin', new Parse.ACL());

setPermissionsOnClass('AClass', {
'find': {
'role:admin': true
}
}).then(() => {
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
}).then(()=> {
role.relation('users').add(admin);
return role.save(null, {useMasterKey: true});
}).then(() => {
return Parse.User.logIn('user', 'user').then(() => {
let obj = new Parse.Object('AClass');
return obj.save();
})
}).then(() => {
let query = new Parse.Query('AClass');
return query.find().then((err) => {
expect(err.message).toEqual('Permission denied for this action.');
fail('User should not be able to find!')
}, (err) => {
return Promise.resolve();
})
}).then(() => {
// delete all CLP
return setPermissionsOnClass('AClass', null, true);
}).then(() => {
let query = new Parse.Query('AClass');
return query.find().then((result) => {
expect(result.length).toBe(1);
}, (err) => {
console.error(err);
fail('User should be able to find!')
done();
});
}).then(() => {
return Parse.User.logIn('admin', 'admin');
}).then( () => {
let query = new Parse.Query('AClass');
return query.find();
}).then((results) => {
expect(results.length).toBe(1);
done();
}, (err) => {
console.error(err);
fail("should not fail!");
done();
}).catch( (err) => {
console.error(err);
done();
})
});

it('validate CLP 4', done => {
let user = new Parse.User();
user.setUsername('user');
user.setPassword('user');

let admin = new Parse.User();
admin.setUsername('admin');
admin.setPassword('admin');

let role = new Parse.Role('admin', new Parse.ACL());

setPermissionsOnClass('AClass', {
'find': {
'role:admin': true
}
}).then(() => {
return Parse.Object.saveAll([user, admin, role], {useMasterKey: true});
}).then(()=> {
role.relation('users').add(admin);
return role.save(null, {useMasterKey: true});
}).then(() => {
return Parse.User.logIn('user', 'user').then(() => {
let obj = new Parse.Object('AClass');
return obj.save();
})
}).then(() => {
let query = new Parse.Query('AClass');
return query.find().then((err) => {
expect(err.message).toEqual('Permission denied for this action.');
fail('User should not be able to find!')
}, (err) => {
return Promise.resolve();
})
}).then(() => {
// borked CLP should not affec security
return setPermissionsOnClass('AClass', {
'found': {
'role:admin': true
}
}, true).then(() => {
fail("Should not be able to save a borked CLP");
}, () => {
return Promise.resolve();
})
}).then(() => {
let query = new Parse.Query('AClass');
return query.find().then((result) => {
fail('User should not be able to find!')
}, (err) => {
expect(err.message).toEqual('Permission denied for this action.');
return Promise.resolve();
});
}).then(() => {
return Parse.User.logIn('admin', 'admin');
}).then( () => {
let query = new Parse.Query('AClass');
return query.find();
}).then((results) => {
expect(results.length).toBe(1);
done();
}, (err) => {
console.error(err);
fail("should not fail!");
done();
}).catch( (err) => {
console.error(err);
done();
})
});

});
38 changes: 20 additions & 18 deletions src/Schema.js
Original file line number Diff line number Diff line change
Expand Up @@ -262,19 +262,11 @@ class Schema {
if (this.data[className]) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
}
if (classLevelPermissions) {
validateCLP(classLevelPermissions);
}

let mongoObject = mongoSchemaFromFieldsAndClassName(fields, className);
let mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions);
if (!mongoObject.result) {
return Promise.reject(mongoObject);
}

if (classLevelPermissions) {
mongoObject.result._metadata = mongoObject.result._metadata || {};
mongoObject.result._metadata.class_permissions = classLevelPermissions;
}

return this._collection.addSchema(className, mongoObject.result)
.then(result => result.ops[0])
Expand All @@ -301,17 +293,12 @@ class Schema {
}
});

validateCLP(classLevelPermissions);
let newSchema = buildMergedSchemaObject(existingFields, submittedFields);
let mongoObject = mongoSchemaFromFieldsAndClassName(newSchema, className);
let mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(newSchema, className, classLevelPermissions);
if (!mongoObject.result) {
throw new Parse.Error(mongoObject.code, mongoObject.error);
}
// set the class permissions
if (classLevelPermissions) {
mongoObject.result._metadata = mongoObject.result._metadata || {};
mongoObject.result._metadata.class_permissions = classLevelPermissions;
}

// Finally we have checked to make sure the request is valid and we can start deleting fields.
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
let deletePromises = [];
Expand All @@ -333,6 +320,9 @@ class Schema {
});
return Promise.all(promises);
})
.then(() => {
return this.setPermission(className, classLevelPermissions)
})
.then(() => { return mongoSchemaToSchemaAPIResponse(mongoObject.result) });
}

Expand Down Expand Up @@ -383,6 +373,9 @@ class Schema {

// Sets the Class-level permissions for a given className, which must exist.
setPermissions(className, perms) {
if (typeof perms === 'undefined') {
return Promise.resolve();
}
validateCLP(perms);
var update = {
_metadata: {
Expand Down Expand Up @@ -644,7 +637,7 @@ function load(collection) {

// Returns { code, error } if invalid, or { result }, an object
// suitable for inserting into _SCHEMA collection, otherwise
function mongoSchemaFromFieldsAndClassName(fields, className) {
function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions) {
if (!classNameIsValid(className)) {
return {
code: Parse.Error.INVALID_CLASS_NAME,
Expand Down Expand Up @@ -697,6 +690,16 @@ function mongoSchemaFromFieldsAndClassName(fields, className) {
error: 'currently, only one GeoPoint field may exist in an object. Adding ' + geoPoints[1] + ' when ' + geoPoints[0] + ' already exists.',
};
}

validateCLP(classLevelPermissions);
if (typeof classLevelPermissions !== 'undefined') {
mongoObject._metadata = mongoObject._metadata || {};
if (!classLevelPermissions) {
delete mongoObject._metadata.class_permissions;
} else {
mongoObject._metadata.class_permissions = classLevelPermissions;
}
}

return { result: mongoObject };
}
Expand Down Expand Up @@ -886,7 +889,6 @@ module.exports = {
load: load,
classNameIsValid: classNameIsValid,
invalidClassNameMessage: invalidClassNameMessage,
mongoSchemaFromFieldsAndClassName: mongoSchemaFromFieldsAndClassName,
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
buildMergedSchemaObject: buildMergedSchemaObject,
mongoFieldTypeToSchemaAPIType: mongoFieldTypeToSchemaAPIType,
Expand Down

0 comments on commit 5f300ca

Please sign in to comment.