Files
kami-parse-server/src/Controllers/HooksController.js
Diamond Lewis ca1b78220f Prevent invalid column names (className and length) (#7053)
* Prevent invalid column names

* remove className as invalid

* remove className from beforeSave hook response

* improve tests
2020-12-09 12:19:15 -06:00

256 lines
6.8 KiB
JavaScript

/** @flow weak */
import * as triggers from '../triggers';
// @flow-disable-next
import * as Parse from 'parse/node';
// @flow-disable-next
import request from '../request';
import { logger } from '../logger';
import http from 'http';
import https from 'https';
const DefaultHooksCollectionName = '_Hooks';
const HTTPAgents = {
http: new http.Agent({ keepAlive: true }),
https: new https.Agent({ keepAlive: true }),
};
export class HooksController {
_applicationId: string;
_webhookKey: string;
database: any;
constructor(applicationId: string, databaseController, webhookKey) {
this._applicationId = applicationId;
this._webhookKey = webhookKey;
this.database = databaseController;
}
load() {
return this._getHooks().then(hooks => {
hooks = hooks || [];
hooks.forEach(hook => {
this.addHookToTriggers(hook);
});
});
}
getFunction(functionName) {
return this._getHooks({ functionName: functionName }).then(results => results[0]);
}
getFunctions() {
return this._getHooks({ functionName: { $exists: true } });
}
getTrigger(className, triggerName) {
return this._getHooks({
className: className,
triggerName: triggerName,
}).then(results => results[0]);
}
getTriggers() {
return this._getHooks({
className: { $exists: true },
triggerName: { $exists: true },
});
}
deleteFunction(functionName) {
triggers.removeFunction(functionName, this._applicationId);
return this._removeHooks({ functionName: functionName });
}
deleteTrigger(className, triggerName) {
triggers.removeTrigger(triggerName, className, this._applicationId);
return this._removeHooks({
className: className,
triggerName: triggerName,
});
}
_getHooks(query = {}) {
return this.database.find(DefaultHooksCollectionName, query).then(results => {
return results.map(result => {
delete result.objectId;
return result;
});
});
}
_removeHooks(query) {
return this.database.destroy(DefaultHooksCollectionName, query).then(() => {
return Promise.resolve({});
});
}
saveHook(hook) {
var query;
if (hook.functionName && hook.url) {
query = { functionName: hook.functionName };
} else if (hook.triggerName && hook.className && hook.url) {
query = { className: hook.className, triggerName: hook.triggerName };
} else {
throw new Parse.Error(143, 'invalid hook declaration');
}
return this.database
.update(DefaultHooksCollectionName, query, hook, { upsert: true })
.then(() => {
return Promise.resolve(hook);
});
}
addHookToTriggers(hook) {
var wrappedFunction = wrapToHTTPRequest(hook, this._webhookKey);
wrappedFunction.url = hook.url;
if (hook.className) {
triggers.addTrigger(hook.triggerName, hook.className, wrappedFunction, this._applicationId);
} else {
triggers.addFunction(hook.functionName, wrappedFunction, null, this._applicationId);
}
}
addHook(hook) {
this.addHookToTriggers(hook);
return this.saveHook(hook);
}
createOrUpdateHook(aHook) {
var hook;
if (aHook && aHook.functionName && aHook.url) {
hook = {};
hook.functionName = aHook.functionName;
hook.url = aHook.url;
} else if (
aHook &&
aHook.className &&
aHook.url &&
aHook.triggerName &&
triggers.Types[aHook.triggerName]
) {
hook = {};
hook.className = aHook.className;
hook.url = aHook.url;
hook.triggerName = aHook.triggerName;
} else {
throw new Parse.Error(143, 'invalid hook declaration');
}
return this.addHook(hook);
}
createHook(aHook) {
if (aHook.functionName) {
return this.getFunction(aHook.functionName).then(result => {
if (result) {
throw new Parse.Error(143, `function name: ${aHook.functionName} already exits`);
} else {
return this.createOrUpdateHook(aHook);
}
});
} else if (aHook.className && aHook.triggerName) {
return this.getTrigger(aHook.className, aHook.triggerName).then(result => {
if (result) {
throw new Parse.Error(
143,
`class ${aHook.className} already has trigger ${aHook.triggerName}`
);
}
return this.createOrUpdateHook(aHook);
});
}
throw new Parse.Error(143, 'invalid hook declaration');
}
updateHook(aHook) {
if (aHook.functionName) {
return this.getFunction(aHook.functionName).then(result => {
if (result) {
return this.createOrUpdateHook(aHook);
}
throw new Parse.Error(143, `no function named: ${aHook.functionName} is defined`);
});
} else if (aHook.className && aHook.triggerName) {
return this.getTrigger(aHook.className, aHook.triggerName).then(result => {
if (result) {
return this.createOrUpdateHook(aHook);
}
throw new Parse.Error(143, `class ${aHook.className} does not exist`);
});
}
throw new Parse.Error(143, 'invalid hook declaration');
}
}
function wrapToHTTPRequest(hook, key) {
return req => {
const jsonBody = {};
for (var i in req) {
jsonBody[i] = req[i];
}
if (req.object) {
jsonBody.object = req.object.toJSON();
jsonBody.object.className = req.object.className;
}
if (req.original) {
jsonBody.original = req.original.toJSON();
jsonBody.original.className = req.original.className;
}
const jsonRequest: any = {
url: hook.url,
headers: {
'Content-Type': 'application/json',
},
body: jsonBody,
method: 'POST',
};
const agent = hook.url.startsWith('https') ? HTTPAgents['https'] : HTTPAgents['http'];
jsonRequest.agent = agent;
if (key) {
jsonRequest.headers['X-Parse-Webhook-Key'] = key;
} else {
logger.warn('Making outgoing webhook request without webhookKey being set!');
}
return request(jsonRequest).then(response => {
let err;
let result;
let body = response.data;
if (body) {
if (typeof body === 'string') {
try {
body = JSON.parse(body);
} catch (e) {
err = {
error: 'Malformed response',
code: -1,
partialResponse: body.substring(0, 100),
};
}
}
if (!err) {
result = body.success;
err = body.error;
}
}
if (err) {
throw err;
} else if (hook.triggerName === 'beforeSave') {
if (typeof result === 'object') {
delete result.createdAt;
delete result.updatedAt;
delete result.className;
}
return { object: result };
} else {
return result;
}
});
};
}
export default HooksController;