mirror of https://github.com/wekan/wekan
responsible disclosure by [Dejan Zelic](https://twitter.com/dejandayoff), Justin Benjamin and others at [Offensive Security](https://twitter.com/offsectraining), that follow standard 90 days before public disclosure. Thanks to xet7. - Fix webhook error that prevented some card etc deleting from web UI of board. Thanks to xet7. - Add some more Font Awesome icons. Thanks to xet7. - Remove autofocus from many form input boxes so that they would not cause warnings. Thanks to xet7.reviewable/pr2955/r1
parent
fc35c234a7
commit
aac7c380c8
@ -1,192 +1,199 @@ |
|||||||
const postCatchError = Meteor.wrapAsync((url, options, resolve) => { |
if (Meteor.isServer) { |
||||||
HTTP.post(url, options, (err, res) => { |
const postCatchError = Meteor.wrapAsync((url, options, resolve) => { |
||||||
if (err) { |
HTTP.post(url, options, (err, res) => { |
||||||
resolve(null, err.response); |
if (err) { |
||||||
} else { |
resolve(null, err.response); |
||||||
resolve(null, res); |
} else { |
||||||
} |
resolve(null, res); |
||||||
|
} |
||||||
|
}); |
||||||
}); |
}); |
||||||
}); |
|
||||||
|
|
||||||
const Lock = { |
const Lock = { |
||||||
_lock: {}, |
_lock: {}, |
||||||
_timer: {}, |
_timer: {}, |
||||||
echoDelay: 500, // echo should be happening much faster
|
echoDelay: 500, // echo should be happening much faster
|
||||||
normalDelay: 1e3, // normally user typed comment will be much slower
|
normalDelay: 1e3, // normally user typed comment will be much slower
|
||||||
ECHO: 2, |
ECHO: 2, |
||||||
NORMAL: 1, |
NORMAL: 1, |
||||||
NULL: 0, |
NULL: 0, |
||||||
has(id, value) { |
has(id, value) { |
||||||
const existing = this._lock[id]; |
const existing = this._lock[id]; |
||||||
let ret = this.NULL; |
let ret = this.NULL; |
||||||
if (existing) { |
if (existing) { |
||||||
ret = existing === value ? this.ECHO : this.NORMAL; |
ret = existing === value ? this.ECHO : this.NORMAL; |
||||||
} |
} |
||||||
return ret; |
return ret; |
||||||
}, |
}, |
||||||
clear(id, delay) { |
clear(id, delay) { |
||||||
const previous = this._timer[id]; |
const previous = this._timer[id]; |
||||||
if (previous) { |
if (previous) { |
||||||
Meteor.clearTimeout(previous); |
Meteor.clearTimeout(previous); |
||||||
} |
} |
||||||
this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay); |
this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay); |
||||||
}, |
}, |
||||||
set(id, value) { |
set(id, value) { |
||||||
const state = this.has(id, value); |
const state = this.has(id, value); |
||||||
let delay = this.normalDelay; |
let delay = this.normalDelay; |
||||||
if (state === this.ECHO) { |
if (state === this.ECHO) { |
||||||
delay = this.echoDelay; |
delay = this.echoDelay; |
||||||
} |
} |
||||||
if (!value) { |
if (!value) { |
||||||
// user commented, we set a lock
|
// user commented, we set a lock
|
||||||
value = 1; |
value = 1; |
||||||
} |
} |
||||||
this._lock[id] = value; |
this._lock[id] = value; |
||||||
this.clear(id, delay); // always auto reset the locker after delay
|
this.clear(id, delay); // always auto reset the locker after delay
|
||||||
}, |
}, |
||||||
unset(id) { |
unset(id) { |
||||||
delete this._lock[id]; |
delete this._lock[id]; |
||||||
}, |
}, |
||||||
}; |
}; |
||||||
|
|
||||||
const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES && |
const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES && |
||||||
process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [ |
process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [ |
||||||
'cardId', |
'cardId', |
||||||
'listId', |
'listId', |
||||||
'oldListId', |
'oldListId', |
||||||
'boardId', |
'boardId', |
||||||
'comment', |
'comment', |
||||||
'user', |
'user', |
||||||
'card', |
'card', |
||||||
'commentId', |
'commentId', |
||||||
'swimlaneId', |
'swimlaneId', |
||||||
]; |
]; |
||||||
const responseFunc = data => { |
const responseFunc = data => { |
||||||
const paramCommentId = data.commentId; |
const paramCommentId = data.commentId; |
||||||
const paramCardId = data.cardId; |
const paramCardId = data.cardId; |
||||||
const paramBoardId = data.boardId; |
const paramBoardId = data.boardId; |
||||||
const newComment = data.comment; |
const newComment = data.comment; |
||||||
if (paramCardId && paramBoardId && newComment) { |
if (paramCardId && paramBoardId && newComment) { |
||||||
// only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data
|
// only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data
|
||||||
const comment = CardComments.findOne({ |
const comment = CardComments.findOne({ |
||||||
_id: paramCommentId, |
_id: paramCommentId, |
||||||
cardId: paramCardId, |
cardId: paramCardId, |
||||||
boardId: paramBoardId, |
boardId: paramBoardId, |
||||||
}); |
}); |
||||||
const board = Boards.findOne(paramBoardId); |
const board = Boards.findOne(paramBoardId); |
||||||
const card = Cards.findOne(paramCardId); |
const card = Cards.findOne(paramCardId); |
||||||
if (board && card) { |
if (board && card) { |
||||||
if (comment) { |
if (comment) { |
||||||
Lock.set(comment._id, newComment); |
Lock.set(comment._id, newComment); |
||||||
CardComments.direct.update(comment._id, { |
CardComments.direct.update(comment._id, { |
||||||
$set: { |
$set: { |
||||||
|
text: newComment, |
||||||
|
}, |
||||||
|
}); |
||||||
|
} |
||||||
|
} else { |
||||||
|
const userId = data.userId; |
||||||
|
if (userId) { |
||||||
|
const inserted = CardComments.direct.insert({ |
||||||
text: newComment, |
text: newComment, |
||||||
}, |
userId, |
||||||
}); |
cardId, |
||||||
} |
boardId, |
||||||
} else { |
}); |
||||||
const userId = data.userId; |
Lock.set(inserted._id, newComment); |
||||||
if (userId) { |
} |
||||||
const inserted = CardComments.direct.insert({ |
|
||||||
text: newComment, |
|
||||||
userId, |
|
||||||
cardId, |
|
||||||
boardId, |
|
||||||
}); |
|
||||||
Lock.set(inserted._id, newComment); |
|
||||||
} |
} |
||||||
} |
} |
||||||
} |
}; |
||||||
}; |
Meteor.methods({ |
||||||
Meteor.methods({ |
outgoingWebhooks(integration, description, params) { |
||||||
outgoingWebhooks(integration, description, params) { |
if (Meteor.user()) { |
||||||
check(integration, Object); |
check(integration, Object); |
||||||
check(description, String); |
check(description, String); |
||||||
check(params, Object); |
check(params, Object); |
||||||
this.unblock(); |
this.unblock(); |
||||||
|
|
||||||
// label activity did not work yet, see wekan/models/activities.js
|
// label activity did not work yet, see wekan/models/activities.js
|
||||||
const quoteParams = _.clone(params); |
const quoteParams = _.clone(params); |
||||||
const clonedParams = _.clone(params); |
const clonedParams = _.clone(params); |
||||||
[ |
[ |
||||||
'card', |
'card', |
||||||
'list', |
'list', |
||||||
'oldList', |
'oldList', |
||||||
'board', |
'board', |
||||||
'oldBoard', |
'oldBoard', |
||||||
'comment', |
'comment', |
||||||
'checklist', |
'checklist', |
||||||
'swimlane', |
'swimlane', |
||||||
'oldSwimlane', |
'oldSwimlane', |
||||||
'label', |
'label', |
||||||
'attachment', |
'attachment', |
||||||
].forEach(key => { |
].forEach(key => { |
||||||
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`; |
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`; |
||||||
}); |
}); |
||||||
|
|
||||||
const userId = params.userId ? params.userId : integrations[0].userId; |
const userId = params.userId ? params.userId : integrations[0].userId; |
||||||
const user = Users.findOne(userId); |
const user = Users.findOne(userId); |
||||||
const text = `${params.user} ${TAPi18n.__( |
const text = `${params.user} ${TAPi18n.__( |
||||||
description, |
description, |
||||||
quoteParams, |
quoteParams, |
||||||
user.getLanguage(), |
user.getLanguage(), |
||||||
)}\n${params.url}`;
|
)}\n${params.url}`;
|
||||||
|
|
||||||
if (text.length === 0) return; |
if (text.length === 0) return; |
||||||
|
|
||||||
const value = { |
const value = { |
||||||
text: `${text}`, |
text: `${text}`, |
||||||
}; |
}; |
||||||
|
|
||||||
webhooksAtbts.forEach(key => { |
webhooksAtbts.forEach(key => { |
||||||
if (params[key]) value[key] = params[key]; |
if (params[key]) value[key] = params[key]; |
||||||
}); |
}); |
||||||
value.description = description; |
value.description = description; |
||||||
//integrations.forEach(integration => {
|
//integrations.forEach(integration => {
|
||||||
const is2way = integration.type === Integrations.Const.TWOWAY; |
const is2way = integration.type === Integrations.Const.TWOWAY; |
||||||
const token = integration.token || ''; |
const token = integration.token || ''; |
||||||
const headers = { |
const headers = { |
||||||
'Content-Type': 'application/json', |
'Content-Type': 'application/json', |
||||||
}; |
}; |
||||||
if (token) headers['X-Wekan-Token'] = token; |
if (token) headers['X-Wekan-Token'] = token; |
||||||
const options = { |
const options = { |
||||||
headers, |
headers, |
||||||
data: is2way ? { description, ...clonedParams } : value, |
data: is2way ? { description, ...clonedParams } : value, |
||||||
}; |
}; |
||||||
const url = integration.url; |
|
||||||
if (is2way) { |
|
||||||
const cid = params.commentId; |
|
||||||
const comment = params.comment; |
|
||||||
const lockState = cid && Lock.has(cid, comment); |
|
||||||
if (cid && lockState !== Lock.NULL) { |
|
||||||
// it's a comment and there is a previous lock
|
|
||||||
return; |
|
||||||
} else if (cid) { |
|
||||||
Lock.set(cid, comment); // set a lock here
|
|
||||||
} |
|
||||||
} |
|
||||||
const response = postCatchError(url, options); |
|
||||||
|
|
||||||
if ( |
if (!Integrations.findOne({ url: integration.url })) return; |
||||||
response && |
|
||||||
response.statusCode && |
const url = integration.url; |
||||||
response.statusCode >= 200 && |
|
||||||
response.statusCode < 300 |
if (is2way) { |
||||||
) { |
const cid = params.commentId; |
||||||
if (is2way) { |
const comment = params.comment; |
||||||
const data = response.data; // only an JSON encoded response will be actioned
|
const lockState = cid && Lock.has(cid, comment); |
||||||
if (data) { |
if (cid && lockState !== Lock.NULL) { |
||||||
try { |
// it's a comment and there is a previous lock
|
||||||
responseFunc(data); |
return; |
||||||
} catch (e) { |
} else if (cid) { |
||||||
throw new Meteor.Error('error-process-data'); |
Lock.set(cid, comment); // set a lock here
|
||||||
} |
} |
||||||
} |
} |
||||||
|
const response = postCatchError(url, options); |
||||||
|
|
||||||
|
if ( |
||||||
|
response && |
||||||
|
response.statusCode && |
||||||
|
response.statusCode >= 200 && |
||||||
|
response.statusCode < 300 |
||||||
|
) { |
||||||
|
if (is2way) { |
||||||
|
const data = response.data; // only an JSON encoded response will be actioned
|
||||||
|
if (data) { |
||||||
|
try { |
||||||
|
responseFunc(data); |
||||||
|
} catch (e) { |
||||||
|
throw new Meteor.Error('error-process-data'); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return response; // eslint-disable-line consistent-return
|
||||||
|
} else { |
||||||
|
throw new Meteor.Error('error-invalid-webhook-response'); |
||||||
|
} |
||||||
} |
} |
||||||
return response; // eslint-disable-line consistent-return
|
}, |
||||||
} else { |
}); |
||||||
throw new Meteor.Error('error-invalid-webhook-response'); |
} |
||||||
} |
|
||||||
//});
|
|
||||||
}, |
|
||||||
}); |
|
||||||
|
@ -1,68 +1,76 @@ |
|||||||
import { MongoInternals } from 'meteor/mongo'; |
import { MongoInternals } from 'meteor/mongo'; |
||||||
|
|
||||||
Meteor.methods({ |
if (Meteor.isServer) { |
||||||
getStatistics() { |
Meteor.methods({ |
||||||
const os = require('os'); |
getStatistics() { |
||||||
const pjson = require('/package.json'); |
if (Meteor.user() && Meteor.user().isAdmin) { |
||||||
const statistics = {}; |
const os = require('os'); |
||||||
let wekanVersion = pjson.version; |
const pjson = require('/package.json'); |
||||||
wekanVersion = wekanVersion.replace('v', ''); |
const statistics = {}; |
||||||
statistics.version = wekanVersion; |
let wekanVersion = pjson.version; |
||||||
statistics.os = { |
wekanVersion = wekanVersion.replace('v', ''); |
||||||
type: os.type(), |
statistics.version = wekanVersion; |
||||||
platform: os.platform(), |
statistics.os = { |
||||||
arch: os.arch(), |
type: os.type(), |
||||||
release: os.release(), |
platform: os.platform(), |
||||||
uptime: os.uptime(), |
arch: os.arch(), |
||||||
loadavg: os.loadavg(), |
release: os.release(), |
||||||
totalmem: os.totalmem(), |
uptime: os.uptime(), |
||||||
freemem: os.freemem(), |
loadavg: os.loadavg(), |
||||||
cpus: os.cpus(), |
totalmem: os.totalmem(), |
||||||
}; |
freemem: os.freemem(), |
||||||
let nodeVersion = process.version; |
cpus: os.cpus(), |
||||||
nodeVersion = nodeVersion.replace('v', ''); |
}; |
||||||
statistics.process = { |
let nodeVersion = process.version; |
||||||
nodeVersion, |
nodeVersion = nodeVersion.replace('v', ''); |
||||||
pid: process.pid, |
statistics.process = { |
||||||
uptime: process.uptime(), |
nodeVersion, |
||||||
}; |
pid: process.pid, |
||||||
// Remove beginning of Meteor release text METEOR@
|
uptime: process.uptime(), |
||||||
let meteorVersion = Meteor.release; |
}; |
||||||
meteorVersion = meteorVersion.replace('METEOR@', ''); |
// Remove beginning of Meteor release text METEOR@
|
||||||
statistics.meteor = { |
let meteorVersion = Meteor.release; |
||||||
meteorVersion, |
meteorVersion = meteorVersion.replace('METEOR@', ''); |
||||||
}; |
statistics.meteor = { |
||||||
// Thanks to RocketChat for MongoDB version detection !
|
meteorVersion, |
||||||
// https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
|
}; |
||||||
let mongoVersion; |
// Thanks to RocketChat for MongoDB version detection !
|
||||||
let mongoStorageEngine; |
// https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
|
||||||
let mongoOplogEnabled; |
let mongoVersion; |
||||||
try { |
let mongoStorageEngine; |
||||||
const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); |
let mongoOplogEnabled; |
||||||
oplogEnabled = Boolean( |
try { |
||||||
mongo._oplogHandle && mongo._oplogHandle.onOplogEntry, |
const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); |
||||||
); |
oplogEnabled = Boolean( |
||||||
const { version, storageEngine } = Promise.await( |
mongo._oplogHandle && mongo._oplogHandle.onOplogEntry, |
||||||
mongo.db.command({ serverStatus: 1 }), |
); |
||||||
); |
const { version, storageEngine } = Promise.await( |
||||||
mongoVersion = version; |
mongo.db.command({ serverStatus: 1 }), |
||||||
mongoStorageEngine = storageEngine.name; |
); |
||||||
mongoOplogEnabled = oplogEnabled; |
mongoVersion = version; |
||||||
} catch (e) { |
mongoStorageEngine = storageEngine.name; |
||||||
try { |
mongoOplogEnabled = oplogEnabled; |
||||||
const { version } = Promise.await(mongo.db.command({ buildinfo: 1 })); |
} catch (e) { |
||||||
mongoVersion = version; |
try { |
||||||
mongoStorageEngine = 'unknown'; |
const { version } = Promise.await( |
||||||
} catch (e) { |
mongo.db.command({ buildinfo: 1 }), |
||||||
mongoVersion = 'unknown'; |
); |
||||||
mongoStorageEngine = 'unknown'; |
mongoVersion = version; |
||||||
|
mongoStorageEngine = 'unknown'; |
||||||
|
} catch (e) { |
||||||
|
mongoVersion = 'unknown'; |
||||||
|
mongoStorageEngine = 'unknown'; |
||||||
|
} |
||||||
|
} |
||||||
|
statistics.mongo = { |
||||||
|
mongoVersion, |
||||||
|
mongoStorageEngine, |
||||||
|
mongoOplogEnabled, |
||||||
|
}; |
||||||
|
return statistics; |
||||||
|
} else { |
||||||
|
return false; |
||||||
} |
} |
||||||
} |
}, |
||||||
statistics.mongo = { |
}); |
||||||
mongoVersion, |
} |
||||||
mongoStorageEngine, |
|
||||||
mongoOplogEnabled, |
|
||||||
}; |
|
||||||
return statistics; |
|
||||||
}, |
|
||||||
}); |
|
||||||
|
Loading…
Reference in new issue