summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorLauri Ojansivu <x@xet7.org>2020-03-01 20:59:53 +0200
committerLauri Ojansivu <x@xet7.org>2020-03-01 20:59:53 +0200
commitaac7c380c8c389b0683b2bd64e2cc856993f0e30 (patch)
tree8d76eeb0202a1ae456e7d96c3ee59b83cfb77094 /server
parentfc35c234a78fb2137f0f78a3a6f353c46734ed72 (diff)
downloadwekan-aac7c380c8c389b0683b2bd64e2cc856993f0e30.tar.gz
wekan-aac7c380c8c389b0683b2bd64e2cc856993f0e30.tar.bz2
wekan-aac7c380c8c389b0683b2bd64e2cc856993f0e30.zip
- Fix critical and moderate security vulnerabilities reported at 2020-02-26 with
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.
Diffstat (limited to 'server')
-rw-r--r--server/notifications/outgoing.js361
-rw-r--r--server/statistics.js138
2 files changed, 257 insertions, 242 deletions
diff --git a/server/notifications/outgoing.js b/server/notifications/outgoing.js
index 5bc2c540..9a741ea1 100644
--- a/server/notifications/outgoing.js
+++ b/server/notifications/outgoing.js
@@ -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');
- }
- //});
- },
-});
+ },
+ });
+}
diff --git a/server/statistics.js b/server/statistics.js
index 997fd86f..0ead840f 100644
--- a/server/statistics.js
+++ b/server/statistics.js
@@ -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;
- },
-});
+ },
+ });
+}