Adds tests, improve coverage, adds ability to delete CLP with classLevelPermissions: null
This commit is contained in:
@@ -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();
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -262,20 +262,12 @@ class Schema {
|
|||||||
if (this.data[className]) {
|
if (this.data[className]) {
|
||||||
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} already exists.`);
|
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) {
|
if (!mongoObject.result) {
|
||||||
return Promise.reject(mongoObject);
|
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)
|
return this._collection.addSchema(className, mongoObject.result)
|
||||||
.then(result => result.ops[0])
|
.then(result => result.ops[0])
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
@@ -301,17 +293,12 @@ class Schema {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
validateCLP(classLevelPermissions);
|
|
||||||
let newSchema = buildMergedSchemaObject(existingFields, submittedFields);
|
let newSchema = buildMergedSchemaObject(existingFields, submittedFields);
|
||||||
let mongoObject = mongoSchemaFromFieldsAndClassName(newSchema, className);
|
let mongoObject = mongoSchemaFromFieldsAndClassNameAndCLP(newSchema, className, classLevelPermissions);
|
||||||
if (!mongoObject.result) {
|
if (!mongoObject.result) {
|
||||||
throw new Parse.Error(mongoObject.code, mongoObject.error);
|
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.
|
// 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.
|
// Do all deletions first, then a single save to _SCHEMA collection to handle all additions.
|
||||||
let deletePromises = [];
|
let deletePromises = [];
|
||||||
@@ -333,6 +320,9 @@ class Schema {
|
|||||||
});
|
});
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
})
|
})
|
||||||
|
.then(() => {
|
||||||
|
return this.setPermissions(className, classLevelPermissions)
|
||||||
|
})
|
||||||
.then(() => { return mongoSchemaToSchemaAPIResponse(mongoObject.result) });
|
.then(() => { return mongoSchemaToSchemaAPIResponse(mongoObject.result) });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,6 +373,9 @@ class Schema {
|
|||||||
|
|
||||||
// Sets the Class-level permissions for a given className, which must exist.
|
// Sets the Class-level permissions for a given className, which must exist.
|
||||||
setPermissions(className, perms) {
|
setPermissions(className, perms) {
|
||||||
|
if (typeof perms === 'undefined') {
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
validateCLP(perms);
|
validateCLP(perms);
|
||||||
var update = {
|
var update = {
|
||||||
_metadata: {
|
_metadata: {
|
||||||
@@ -644,7 +637,7 @@ function load(collection) {
|
|||||||
|
|
||||||
// Returns { code, error } if invalid, or { result }, an object
|
// Returns { code, error } if invalid, or { result }, an object
|
||||||
// suitable for inserting into _SCHEMA collection, otherwise
|
// suitable for inserting into _SCHEMA collection, otherwise
|
||||||
function mongoSchemaFromFieldsAndClassName(fields, className) {
|
function mongoSchemaFromFieldsAndClassNameAndCLP(fields, className, classLevelPermissions) {
|
||||||
if (!classNameIsValid(className)) {
|
if (!classNameIsValid(className)) {
|
||||||
return {
|
return {
|
||||||
code: Parse.Error.INVALID_CLASS_NAME,
|
code: Parse.Error.INVALID_CLASS_NAME,
|
||||||
@@ -698,6 +691,16 @@ function mongoSchemaFromFieldsAndClassName(fields, className) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 };
|
return { result: mongoObject };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -886,7 +889,6 @@ module.exports = {
|
|||||||
load: load,
|
load: load,
|
||||||
classNameIsValid: classNameIsValid,
|
classNameIsValid: classNameIsValid,
|
||||||
invalidClassNameMessage: invalidClassNameMessage,
|
invalidClassNameMessage: invalidClassNameMessage,
|
||||||
mongoSchemaFromFieldsAndClassName: mongoSchemaFromFieldsAndClassName,
|
|
||||||
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
|
schemaAPITypeToMongoFieldType: schemaAPITypeToMongoFieldType,
|
||||||
buildMergedSchemaObject: buildMergedSchemaObject,
|
buildMergedSchemaObject: buildMergedSchemaObject,
|
||||||
mongoFieldTypeToSchemaAPIType: mongoFieldTypeToSchemaAPIType,
|
mongoFieldTypeToSchemaAPIType: mongoFieldTypeToSchemaAPIType,
|
||||||
|
|||||||
Reference in New Issue
Block a user