From 5780c1e4250e34b367b4fe2c8317ed348183b242 Mon Sep 17 00:00:00 2001 From: Florent Vilmart Date: Mon, 7 Mar 2016 21:38:20 -0500 Subject: [PATCH] Merges CLP endpoints with POST, PUT and GET --- spec/schemas.spec.js | 66 +++++++++++++++++----------------- src/Routers/SchemasRouter.js | 60 +++---------------------------- src/Schema.js | 69 ++++++++++++++++++++++++++++++++++-- 3 files changed, 104 insertions(+), 91 deletions(-) diff --git a/spec/schemas.spec.js b/spec/schemas.spec.js index 312bd070..bb40dbc2 100644 --- a/spec/schemas.spec.js +++ b/spec/schemas.spec.js @@ -874,14 +874,12 @@ describe('schemas', () => { }); it('should set/get schema permissions', done => { - - let object = new Parse.Object('AClass'); - object.save().then(() => { - request.put({ - url: 'http://localhost:8378/1/schemas/AClass/permissions', - headers: masterKeyHeaders, - json: true, - body: { + request.post({ + url: 'http://localhost:8378/1/schemas/AClass', + headers: masterKeyHeaders, + json: true, + body: { + classLevelPermissions: { find: { '*': true }, @@ -889,24 +887,24 @@ describe('schemas', () => { '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) => { - expect(error).toEqual(null); - request.get({ - url: 'http://localhost:8378/1/schemas/AClass/permissions', - headers: masterKeyHeaders, - json: true, - }, (error, response, body) => { - expect(response.statusCode).toEqual(200); - expect(response.body).toEqual({ - find: { - '*': true - }, - create: { - 'role:admin': true - } - }); - done(); + expect(response.statusCode).toEqual(200); + expect(response.body.classLevelPermissions).toEqual({ + find: { + '*': true + }, + create: { + 'role:admin': true + } }); + done(); }); }); }); @@ -916,18 +914,20 @@ describe('schemas', () => { let object = new Parse.Object('AClass'); object.save().then(() => { request.put({ - url: 'http://localhost:8378/1/schemas/AClass/permissions', + url: 'http://localhost:8378/1/schemas/AClass', headers: masterKeyHeaders, json: true, body: { - find: { - '*': true - }, - create: { - 'role:admin': true - }, - dummy: { - 'some': true + classLevelPermissions: { + find: { + '*': true + }, + create: { + 'role:admin': true + }, + dummy: { + 'some': true + } } } }, (error, response, body) => { diff --git a/src/Routers/SchemasRouter.js b/src/Routers/SchemasRouter.js index 4f8acbe6..49e4bbb2 100644 --- a/src/Routers/SchemasRouter.js +++ b/src/Routers/SchemasRouter.js @@ -46,7 +46,7 @@ function createSchema(req) { } 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) })); } @@ -60,62 +60,12 @@ function modifySchema(req) { return req.config.database.loadSchema() .then(schema => { - if (!schema.data[className]) { - throw new Parse.Error(Parse.Error.INVALID_CLASS_NAME, `Class ${req.params.className} does not exist.`); - } - - 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) })); + return schema.updateClass(className, submittedFields, req.body.classLevelPermissions, req.config.database); + }).then((result) => { + return Promise.resolve({response: 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) { var className = req.params.className; 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/:className', middleware.promiseEnforceMasterKeyAccess, createSchema); 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); } } diff --git a/src/Schema.js b/src/Schema.js index 496c0b2e..f1090c9c 100644 --- a/src/Schema.js +++ b/src/Schema.js @@ -78,6 +78,9 @@ var requiredColumns = { let CLPValidKeys = ['find', 'get', 'create', 'update', 'delete']; function validateCLP(perms) { + if (!perms) { + return; + } Object.keys(perms).forEach((key) => { if (CLPValidKeys.indexOf(key) == -1) { 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 // have authorization (master key, or client class creation // enabled) before calling this function. - addClassIfNotExists(className, fields) { + addClassIfNotExists(className, fields, classLevelPermissions) { 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); 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]) @@ -248,6 +259,56 @@ class Schema { 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. @@ -785,10 +846,14 @@ function mongoSchemaAPIResponseFields(schema) { } function mongoSchemaToSchemaAPIResponse(schema) { - return { + let result = { className: schema._id, fields: mongoSchemaAPIResponseFields(schema), }; + if (schema._metadata && schema._metadata.class_permissions) { + result.classLevelPermissions = schema._metadata.class_permissions; + } + return result; } module.exports = {