Merges CLP endpoints with POST, PUT and GET
This commit is contained in:
@@ -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) => {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 = {
|
||||||
|
|||||||
Reference in New Issue
Block a user