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) => { |
||||
HTTP.post(url, options, (err, res) => { |
||||
if (err) { |
||||
resolve(null, err.response); |
||||
} else { |
||||
resolve(null, res); |
||||
} |
||||
if (Meteor.isServer) { |
||||
const postCatchError = Meteor.wrapAsync((url, options, resolve) => { |
||||
HTTP.post(url, options, (err, res) => { |
||||
if (err) { |
||||
resolve(null, err.response); |
||||
} else { |
||||
resolve(null, res); |
||||
} |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
const Lock = { |
||||
_lock: {}, |
||||
_timer: {}, |
||||
echoDelay: 500, // echo should be happening much faster
|
||||
normalDelay: 1e3, // normally user typed comment will be much slower
|
||||
ECHO: 2, |
||||
NORMAL: 1, |
||||
NULL: 0, |
||||
has(id, value) { |
||||
const existing = this._lock[id]; |
||||
let ret = this.NULL; |
||||
if (existing) { |
||||
ret = existing === value ? this.ECHO : this.NORMAL; |
||||
} |
||||
return ret; |
||||
}, |
||||
clear(id, delay) { |
||||
const previous = this._timer[id]; |
||||
if (previous) { |
||||
Meteor.clearTimeout(previous); |
||||
} |
||||
this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay); |
||||
}, |
||||
set(id, value) { |
||||
const state = this.has(id, value); |
||||
let delay = this.normalDelay; |
||||
if (state === this.ECHO) { |
||||
delay = this.echoDelay; |
||||
} |
||||
if (!value) { |
||||
// user commented, we set a lock
|
||||
value = 1; |
||||
} |
||||
this._lock[id] = value; |
||||
this.clear(id, delay); // always auto reset the locker after delay
|
||||
}, |
||||
unset(id) { |
||||
delete this._lock[id]; |
||||
}, |
||||
}; |
||||
const Lock = { |
||||
_lock: {}, |
||||
_timer: {}, |
||||
echoDelay: 500, // echo should be happening much faster
|
||||
normalDelay: 1e3, // normally user typed comment will be much slower
|
||||
ECHO: 2, |
||||
NORMAL: 1, |
||||
NULL: 0, |
||||
has(id, value) { |
||||
const existing = this._lock[id]; |
||||
let ret = this.NULL; |
||||
if (existing) { |
||||
ret = existing === value ? this.ECHO : this.NORMAL; |
||||
} |
||||
return ret; |
||||
}, |
||||
clear(id, delay) { |
||||
const previous = this._timer[id]; |
||||
if (previous) { |
||||
Meteor.clearTimeout(previous); |
||||
} |
||||
this._timer[id] = Meteor.setTimeout(() => this.unset(id), delay); |
||||
}, |
||||
set(id, value) { |
||||
const state = this.has(id, value); |
||||
let delay = this.normalDelay; |
||||
if (state === this.ECHO) { |
||||
delay = this.echoDelay; |
||||
} |
||||
if (!value) { |
||||
// user commented, we set a lock
|
||||
value = 1; |
||||
} |
||||
this._lock[id] = value; |
||||
this.clear(id, delay); // always auto reset the locker after delay
|
||||
}, |
||||
unset(id) { |
||||
delete this._lock[id]; |
||||
}, |
||||
}; |
||||
|
||||
const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES && |
||||
process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [ |
||||
'cardId', |
||||
'listId', |
||||
'oldListId', |
||||
'boardId', |
||||
'comment', |
||||
'user', |
||||
'card', |
||||
'commentId', |
||||
'swimlaneId', |
||||
]; |
||||
const responseFunc = data => { |
||||
const paramCommentId = data.commentId; |
||||
const paramCardId = data.cardId; |
||||
const paramBoardId = data.boardId; |
||||
const newComment = data.comment; |
||||
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
|
||||
const comment = CardComments.findOne({ |
||||
_id: paramCommentId, |
||||
cardId: paramCardId, |
||||
boardId: paramBoardId, |
||||
}); |
||||
const board = Boards.findOne(paramBoardId); |
||||
const card = Cards.findOne(paramCardId); |
||||
if (board && card) { |
||||
if (comment) { |
||||
Lock.set(comment._id, newComment); |
||||
CardComments.direct.update(comment._id, { |
||||
$set: { |
||||
const webhooksAtbts = (process.env.WEBHOOKS_ATTRIBUTES && |
||||
process.env.WEBHOOKS_ATTRIBUTES.split(',')) || [ |
||||
'cardId', |
||||
'listId', |
||||
'oldListId', |
||||
'boardId', |
||||
'comment', |
||||
'user', |
||||
'card', |
||||
'commentId', |
||||
'swimlaneId', |
||||
]; |
||||
const responseFunc = data => { |
||||
const paramCommentId = data.commentId; |
||||
const paramCardId = data.cardId; |
||||
const paramBoardId = data.boardId; |
||||
const newComment = data.comment; |
||||
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
|
||||
const comment = CardComments.findOne({ |
||||
_id: paramCommentId, |
||||
cardId: paramCardId, |
||||
boardId: paramBoardId, |
||||
}); |
||||
const board = Boards.findOne(paramBoardId); |
||||
const card = Cards.findOne(paramCardId); |
||||
if (board && card) { |
||||
if (comment) { |
||||
Lock.set(comment._id, newComment); |
||||
CardComments.direct.update(comment._id, { |
||||
$set: { |
||||
text: newComment, |
||||
}, |
||||
}); |
||||
} |
||||
} else { |
||||
const userId = data.userId; |
||||
if (userId) { |
||||
const inserted = CardComments.direct.insert({ |
||||
text: newComment, |
||||
}, |
||||
}); |
||||
} |
||||
} else { |
||||
const userId = data.userId; |
||||
if (userId) { |
||||
const inserted = CardComments.direct.insert({ |
||||
text: newComment, |
||||
userId, |
||||
cardId, |
||||
boardId, |
||||
}); |
||||
Lock.set(inserted._id, newComment); |
||||
userId, |
||||
cardId, |
||||
boardId, |
||||
}); |
||||
Lock.set(inserted._id, newComment); |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
Meteor.methods({ |
||||
outgoingWebhooks(integration, description, params) { |
||||
check(integration, Object); |
||||
check(description, String); |
||||
check(params, Object); |
||||
this.unblock(); |
||||
}; |
||||
Meteor.methods({ |
||||
outgoingWebhooks(integration, description, params) { |
||||
if (Meteor.user()) { |
||||
check(integration, Object); |
||||
check(description, String); |
||||
check(params, Object); |
||||
this.unblock(); |
||||
|
||||
// label activity did not work yet, see wekan/models/activities.js
|
||||
const quoteParams = _.clone(params); |
||||
const clonedParams = _.clone(params); |
||||
[ |
||||
'card', |
||||
'list', |
||||
'oldList', |
||||
'board', |
||||
'oldBoard', |
||||
'comment', |
||||
'checklist', |
||||
'swimlane', |
||||
'oldSwimlane', |
||||
'label', |
||||
'attachment', |
||||
].forEach(key => { |
||||
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`; |
||||
}); |
||||
// label activity did not work yet, see wekan/models/activities.js
|
||||
const quoteParams = _.clone(params); |
||||
const clonedParams = _.clone(params); |
||||
[ |
||||
'card', |
||||
'list', |
||||
'oldList', |
||||
'board', |
||||
'oldBoard', |
||||
'comment', |
||||
'checklist', |
||||
'swimlane', |
||||
'oldSwimlane', |
||||
'label', |
||||
'attachment', |
||||
].forEach(key => { |
||||
if (quoteParams[key]) quoteParams[key] = `"${params[key]}"`; |
||||
}); |
||||
|
||||
const userId = params.userId ? params.userId : integrations[0].userId; |
||||
const user = Users.findOne(userId); |
||||
const text = `${params.user} ${TAPi18n.__( |
||||
description, |
||||
quoteParams, |
||||
user.getLanguage(), |
||||
)}\n${params.url}`;
|
||||
const userId = params.userId ? params.userId : integrations[0].userId; |
||||
const user = Users.findOne(userId); |
||||
const text = `${params.user} ${TAPi18n.__( |
||||
description, |
||||
quoteParams, |
||||
user.getLanguage(), |
||||
)}\n${params.url}`;
|
||||
|
||||
if (text.length === 0) return; |
||||
if (text.length === 0) return; |
||||
|
||||
const value = { |
||||
text: `${text}`, |
||||
}; |
||||
const value = { |
||||
text: `${text}`, |
||||
}; |
||||
|
||||
webhooksAtbts.forEach(key => { |
||||
if (params[key]) value[key] = params[key]; |
||||
}); |
||||
value.description = description; |
||||
//integrations.forEach(integration => {
|
||||
const is2way = integration.type === Integrations.Const.TWOWAY; |
||||
const token = integration.token || ''; |
||||
const headers = { |
||||
'Content-Type': 'application/json', |
||||
}; |
||||
if (token) headers['X-Wekan-Token'] = token; |
||||
const options = { |
||||
headers, |
||||
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); |
||||
webhooksAtbts.forEach(key => { |
||||
if (params[key]) value[key] = params[key]; |
||||
}); |
||||
value.description = description; |
||||
//integrations.forEach(integration => {
|
||||
const is2way = integration.type === Integrations.Const.TWOWAY; |
||||
const token = integration.token || ''; |
||||
const headers = { |
||||
'Content-Type': 'application/json', |
||||
}; |
||||
if (token) headers['X-Wekan-Token'] = token; |
||||
const options = { |
||||
headers, |
||||
data: is2way ? { description, ...clonedParams } : value, |
||||
}; |
||||
|
||||
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'); |
||||
if (!Integrations.findOne({ url: integration.url })) return; |
||||
|
||||
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 ( |
||||
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'; |
||||
|
||||
Meteor.methods({ |
||||
getStatistics() { |
||||
const os = require('os'); |
||||
const pjson = require('/package.json'); |
||||
const statistics = {}; |
||||
let wekanVersion = pjson.version; |
||||
wekanVersion = wekanVersion.replace('v', ''); |
||||
statistics.version = wekanVersion; |
||||
statistics.os = { |
||||
type: os.type(), |
||||
platform: os.platform(), |
||||
arch: os.arch(), |
||||
release: os.release(), |
||||
uptime: os.uptime(), |
||||
loadavg: os.loadavg(), |
||||
totalmem: os.totalmem(), |
||||
freemem: os.freemem(), |
||||
cpus: os.cpus(), |
||||
}; |
||||
let nodeVersion = process.version; |
||||
nodeVersion = nodeVersion.replace('v', ''); |
||||
statistics.process = { |
||||
nodeVersion, |
||||
pid: process.pid, |
||||
uptime: process.uptime(), |
||||
}; |
||||
// Remove beginning of Meteor release text METEOR@
|
||||
let meteorVersion = Meteor.release; |
||||
meteorVersion = meteorVersion.replace('METEOR@', ''); |
||||
statistics.meteor = { |
||||
meteorVersion, |
||||
}; |
||||
// Thanks to RocketChat for MongoDB version detection !
|
||||
// https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
|
||||
let mongoVersion; |
||||
let mongoStorageEngine; |
||||
let mongoOplogEnabled; |
||||
try { |
||||
const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); |
||||
oplogEnabled = Boolean( |
||||
mongo._oplogHandle && mongo._oplogHandle.onOplogEntry, |
||||
); |
||||
const { version, storageEngine } = Promise.await( |
||||
mongo.db.command({ serverStatus: 1 }), |
||||
); |
||||
mongoVersion = version; |
||||
mongoStorageEngine = storageEngine.name; |
||||
mongoOplogEnabled = oplogEnabled; |
||||
} catch (e) { |
||||
try { |
||||
const { version } = Promise.await(mongo.db.command({ buildinfo: 1 })); |
||||
mongoVersion = version; |
||||
mongoStorageEngine = 'unknown'; |
||||
} catch (e) { |
||||
mongoVersion = 'unknown'; |
||||
mongoStorageEngine = 'unknown'; |
||||
if (Meteor.isServer) { |
||||
Meteor.methods({ |
||||
getStatistics() { |
||||
if (Meteor.user() && Meteor.user().isAdmin) { |
||||
const os = require('os'); |
||||
const pjson = require('/package.json'); |
||||
const statistics = {}; |
||||
let wekanVersion = pjson.version; |
||||
wekanVersion = wekanVersion.replace('v', ''); |
||||
statistics.version = wekanVersion; |
||||
statistics.os = { |
||||
type: os.type(), |
||||
platform: os.platform(), |
||||
arch: os.arch(), |
||||
release: os.release(), |
||||
uptime: os.uptime(), |
||||
loadavg: os.loadavg(), |
||||
totalmem: os.totalmem(), |
||||
freemem: os.freemem(), |
||||
cpus: os.cpus(), |
||||
}; |
||||
let nodeVersion = process.version; |
||||
nodeVersion = nodeVersion.replace('v', ''); |
||||
statistics.process = { |
||||
nodeVersion, |
||||
pid: process.pid, |
||||
uptime: process.uptime(), |
||||
}; |
||||
// Remove beginning of Meteor release text METEOR@
|
||||
let meteorVersion = Meteor.release; |
||||
meteorVersion = meteorVersion.replace('METEOR@', ''); |
||||
statistics.meteor = { |
||||
meteorVersion, |
||||
}; |
||||
// Thanks to RocketChat for MongoDB version detection !
|
||||
// https://github.com/RocketChat/Rocket.Chat/blob/develop/app/utils/server/functions/getMongoInfo.js
|
||||
let mongoVersion; |
||||
let mongoStorageEngine; |
||||
let mongoOplogEnabled; |
||||
try { |
||||
const { mongo } = MongoInternals.defaultRemoteCollectionDriver(); |
||||
oplogEnabled = Boolean( |
||||
mongo._oplogHandle && mongo._oplogHandle.onOplogEntry, |
||||
); |
||||
const { version, storageEngine } = Promise.await( |
||||
mongo.db.command({ serverStatus: 1 }), |
||||
); |
||||
mongoVersion = version; |
||||
mongoStorageEngine = storageEngine.name; |
||||
mongoOplogEnabled = oplogEnabled; |
||||
} catch (e) { |
||||
try { |
||||
const { version } = Promise.await( |
||||
mongo.db.command({ buildinfo: 1 }), |
||||
); |
||||
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