Merges CLP endpoints with POST, PUT and GET

This commit is contained in:
Florent Vilmart
2016-03-07 21:38:20 -05:00
parent d4fd73100c
commit 5780c1e425
3 changed files with 104 additions and 91 deletions

View File

@@ -874,14 +874,12 @@ describe('schemas', () => {
}); });
it('should set/get schema permissions', done => { it('should set/get schema permissions', done => {
request.post({
let object = new Parse.Object('AClass'); url: 'http://localhost:8378/1/schemas/AClass',
object.save().then(() => { headers: masterKeyHeaders,
request.put({ json: true,
url: 'http://localhost:8378/1/schemas/AClass/permissions', body: {
headers: masterKeyHeaders, classLevelPermissions: {
json: true,
body: {
find: { find: {
'*': true '*': true
}, },
@@ -889,24 +887,24 @@ describe('schemas', () => {
'role:admin': true 'role:admin': true
} }
} }
}
}, (error, response, body) => {
expect(error).toEqual(null);
request.get({
url: 'http://localhost:8378/1/schemas/AClass',
headers: masterKeyHeaders,
json: true,
}, (error, response, body) => { }, (error, response, body) => {
expect(error).toEqual(null); expect(response.statusCode).toEqual(200);
request.get({ expect(response.body.classLevelPermissions).toEqual({
url: 'http://localhost:8378/1/schemas/AClass/permissions', find: {
headers: masterKeyHeaders, '*': true
json: true, },
}, (error, response, body) => { create: {
expect(response.statusCode).toEqual(200); 'role:admin': true
expect(response.body).toEqual({ }
find: {
'*': true
},
create: {
'role:admin': true
}
});
done();
}); });
done();
}); });
}); });
}); });
@@ -916,18 +914,20 @@ describe('schemas', () => {
let object = new Parse.Object('AClass'); let object = new Parse.Object('AClass');
object.save().then(() => { object.save().then(() => {
request.put({ request.put({
url: 'http://localhost:8378/1/schemas/AClass/permissions', url: 'http://localhost:8378/1/schemas/AClass',
headers: masterKeyHeaders, headers: masterKeyHeaders,
json: true, json: true,
body: { body: {
find: { classLevelPermissions: {
'*': true find: {
}, '*': true
create: { },
'role:admin': true create: {
}, 'role:admin': true
dummy: { },
'some': true dummy: {
'some': true
}
} }
} }
}, (error, response, body) => { }, (error, response, body) => {

View File

@@ -46,7 +46,7 @@ function createSchema(req) {
} }
return req.config.database.loadSchema() return req.config.database.loadSchema()
.then(schema => schema.addClassIfNotExists(className, req.body.fields)) .then(schema => schema.addClassIfNotExists(className, req.body.fields, req.body.classLevelPermissions))
.then(result => ({ response: Schema.mongoSchemaToSchemaAPIResponse(result) })); .then(result => ({ response: Schema.mongoSchemaToSchemaAPIResponse(result) }));
} }
@@ -60,62 +60,12 @@ function modifySchema(req) {
return req.config.database.loadSchema() return req.config.database.loadSchema()
.then(schema => { .then(schema => {
if (!schema.data[className]) { return schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database);
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${req.params.className} does not exist.`); }).then((result) => {
} return Promise.resolve({response: result});
let existingFields = Object.assign(schema.data[className], { _id: className });
Object.keys(submittedFields).forEach(name => {
let field = submittedFields[name];
if (existingFields[name] && field.__op !== 'Delete') {
throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
}
if (!existingFields[name] && field.__op === 'Delete') {
throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
}
});
let newSchema = Schema.buildMergedSchemaObject(existingFields, submittedFields);
let mongoObject = Schema.mongoSchemaFromFieldsAndClassName(newSchema, className);
if (!mongoObject.result) {
throw new Parse.Error(mongoObject.code, mongoObject.error);
}
// Finally we have checked to make sure the request is valid and we can start deleting fields.
// Do all deletions first, then add fields to avoid duplicate geopoint error.
let deletePromises = [];
let insertedFields = [];
Object.keys(submittedFields).forEach(fieldName => {
if (submittedFields[fieldName].__op === 'Delete') {
const promise = schema.deleteField(fieldName, className, req.config.database);
deletePromises.push(promise);
} else {
insertedFields.push(fieldName);
}
});
return Promise.all(deletePromises) // Delete Everything
.then(() => schema.reloadData()) // Reload our Schema, so we have all the new values
.then(() => {
let promises = insertedFields.map(fieldName => {
const mongoType = mongoObject.result[fieldName];
return schema.validateField(className, fieldName, mongoType);
});
return Promise.all(promises);
})
.then(() => ({ response: Schema.mongoSchemaToSchemaAPIResponse(mongoObject.result) }));
}); });
} }
function setSchemaPermissions(req) {
var className = req.params.className;
return req.config.database.loadSchema()
.then(schema => {
return schema.setPermissions(className, req.body);
}).then((res) => {
return Promise.resolve({response: {}});
});
}
function getSchemaPermissions(req) { function getSchemaPermissions(req) {
var className = req.params.className; var className = req.params.className;
return req.config.database.loadSchema() return req.config.database.loadSchema()
@@ -189,8 +139,6 @@ export class SchemasRouter extends PromiseRouter {
this.route('POST', '/schemas', middleware.promiseEnforceMasterKeyAccess, createSchema); this.route('POST', '/schemas', middleware.promiseEnforceMasterKeyAccess, createSchema);
this.route('POST', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, createSchema); this.route('POST', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, createSchema);
this.route('PUT', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, modifySchema); this.route('PUT', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, modifySchema);
this.route('GET', '/schemas/:className/permissions', middleware.promiseEnforceMasterKeyAccess, getSchemaPermissions);
this.route('PUT', '/schemas/:className/permissions', middleware.promiseEnforceMasterKeyAccess, setSchemaPermissions);
this.route('DELETE', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, deleteSchema); this.route('DELETE', '/schemas/:className', middleware.promiseEnforceMasterKeyAccess, deleteSchema);
} }
} }

View File

@@ -78,6 +78,9 @@ var requiredColumns = {
let CLPValidKeys = ['find', 'get', 'create', 'update', 'delete']; let CLPValidKeys = ['find', 'get', 'create', 'update', 'delete'];
function validateCLP(perms) { function validateCLP(perms) {
if (!perms) {
return;
}
Object.keys(perms).forEach((key) => { Object.keys(perms).forEach((key) => {
if (CLPValidKeys.indexOf(key) == -1) { if (CLPValidKeys.indexOf(key) == -1) {
throw new Parse.Error(Parse.Error.INVALID_JSON, `${key} is not a valid operation for class level permissions`); throw new Parse.Error(Parse.Error.INVALID_JSON, `${key} is not a valid operation for class level permissions`);
@@ -229,15 +232,23 @@ class Schema {
// on success, and rejects with an error on fail. Ensure you // on success, and rejects with an error on fail. Ensure you
// have authorization (master key, or client class creation // have authorization (master key, or client class creation
// enabled) before calling this function. // enabled) before calling this function.
addClassIfNotExists(className, fields) { addClassIfNotExists(className, fields, classLevelPermissions) {
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 = mongoSchemaFromFieldsAndClassName(fields, className);
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])
@@ -248,6 +259,56 @@ class Schema {
return Promise.reject(error); return Promise.reject(error);
}); });
} }
updateClass(className, submittedFields, classLevelPermissions, database) {
if (!this.data[className]) {
throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${className} does not exist.`);
}
let existingFields = Object.assign(this.data[className], {_id: className});
Object.keys(submittedFields).forEach(name => {
let field = submittedFields[name];
if (existingFields[name] && field.__op !== 'Delete') {
throw new Parse.Error(255, `Field ${name} exists, cannot update.`);
}
if (!existingFields[name] && field.__op === 'Delete') {
throw new Parse.Error(255, `Field ${name} does not exist, cannot delete.`);
}
});
validateCLP(classLevelPermissions);
let newSchema = buildMergedSchemaObject(existingFields, submittedFields);
let mongoObject = mongoSchemaFromFieldsAndClassName(newSchema, className);
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 = [];
let insertedFields = [];
Object.keys(submittedFields).forEach(fieldName => {
if (submittedFields[fieldName].__op === 'Delete') {
const promise = this.deleteField(fieldName, className, database);
deletePromises.push(promise);
} else {
insertedFields.push(fieldName);
}
});
return Promise.all(deletePromises) // Delete Everything
.then(() => this.reloadData()) // Reload our Schema, so we have all the new values
.then(() => {
let promises = insertedFields.map(fieldName => {
const mongoType = mongoObject.result[fieldName];
return this.validateField(className, fieldName, mongoType);
});
return Promise.all(promises);
})
.then(() => { return mongoSchemaToSchemaAPIResponse(mongoObject.result) });
}
// Returns whether the schema knows the type of all these keys. // Returns whether the schema knows the type of all these keys.
@@ -785,10 +846,14 @@ function mongoSchemaAPIResponseFields(schema) {
} }
function mongoSchemaToSchemaAPIResponse(schema) { function mongoSchemaToSchemaAPIResponse(schema) {
return { let result = {
className: schema._id, className: schema._id,
fields: mongoSchemaAPIResponseFields(schema), fields: mongoSchemaAPIResponseFields(schema),
}; };
if (schema._metadata && schema._metadata.class_permissions) {
result.classLevelPermissions = schema._metadata.class_permissions;
}
return result;
} }
module.exports = { module.exports = {