Skip to content

Commit dc230b0

Browse files
Merge pull request #752 from topcoder-platform/develop
PLAT-2614 Ampersand symbol getting converted
2 parents 4d3d43e + 8edf22c commit dc230b0

File tree

11 files changed

+127
-10
lines changed

11 files changed

+127
-10
lines changed

.circleci/config.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ workflows:
154154
- UnitTests
155155
filters:
156156
branches:
157-
only: ['develop', 'connect-performance-testing', 'feature/new-milestone-concept']
157+
only: ['develop', 'connect-performance-testing', 'feature/new-milestone-concept','feature/PLAT-2614']
158158
- deployProd:
159159
context : org-global
160160
requires:

config/default.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
"directProjectServiceEndpoint": "",
1515
"directProjectServiceTimeout": 5000,
1616
"attachmentsS3Bucket": "topcoder-prod-media",
17+
"attachmentsDMZS3Bucket": "topcoder-prod-media-dmz",
18+
"attachmentsQuarantineS3Bucket": "topcoder-prod-media-quarantine",
1719
"projectAttachmentPathPrefix": "projects",
1820
"projectAttachmentPathSuffix": "attachments",
1921
"elasticsearchConfig": {
@@ -87,4 +89,4 @@
8789
},
8890
"STRIPE_SECRET_KEY": "",
8991
"sfdcBillingAccountNameField": "Billing_Account_Name__c"
90-
}
92+
}

config/development.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
"pubsubQueueName": "dev.project.service",
33
"pubsubExchangeName": "dev.projects",
44
"attachmentsS3Bucket": "topcoder-dev-media",
5+
"attachmentsDMZS3Bucket": "topcoder-dev-media-dmz",
6+
"attachmentsQuarantineS3Bucket": "topcoder-dev-media-quarantine",
57
"connectProjectsUrl": "https://connect.topcoder-dev.com/projects/",
68
"fileServiceEndpoint": "https://api.topcoder-dev.com/v3/files/",
79
"connectProjectsUrl": "https://connect.topcoder-dev.com/projects/",

package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@
5757
"express": "^4.13.4",
5858
"express-list-routes": "^0.1.4",
5959
"express-request-id": "^1.1.0",
60-
"express-sanitizer": "^1.0.2",
6160
"express-validation": "^1.0.3",
6261
"handlebars": "^4.5.3",
6362
"http-aws-es": "^4.0.0",
@@ -70,13 +69,14 @@
7069
"moment": "^2.22.2",
7170
"no-kafka": "^3.4.3",
7271
"pg": "^7.11.0",
73-
"pg-native": "^3.0.0",
72+
"pg-native": "^3.0.1",
7473
"sequelize": "^5.8.7",
7574
"stripe": "^8.195.0",
7675
"swagger-ui-express": "^4.0.6",
7776
"tc-core-library-js": "github:appirio-tech/tc-core-library-js#v2.6.6",
7877
"traverse": "^0.6.6",
7978
"urlencode": "^1.1.0",
79+
"xss": "^1.0.14",
8080
"yamljs": "^0.3.0"
8181
},
8282
"devDependencies": {
@@ -102,5 +102,8 @@
102102
"sinon": "^1.17.4",
103103
"sinon-chai": "^2.8.0",
104104
"supertest": "^4.0.2"
105+
},
106+
"volta": {
107+
"node": "12.22.12"
105108
}
106109
}

src/app.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import express from 'express';
22
import methodOverride from 'method-override';
33
import _ from 'lodash';
44
import bodyParser from 'body-parser';
5-
import expressSanitizer from 'express-sanitizer';
65
import config from 'config';
76
import cors from 'cors';
87
import coreLib from 'tc-core-library-js';
@@ -36,7 +35,6 @@ app.use(bodyParser.urlencoded({
3635
extended: false,
3736
}));
3837
app.use(bodyParser.json());
39-
app.use(expressSanitizer());
4038

4139
// add request Id
4240
const addRequestId = expressRequestId();

src/constants.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ export const EVENT = {
139139
};
140140

141141
export const BUS_API_EVENT = {
142+
AV_SCAN_REQUEST: 'avscan.action.scan',
143+
142144
PROJECT_CREATED: 'project.action.create',
143145
PROJECT_UPDATED: 'project.action.update',
144146
PROJECT_DELETED: 'project.action.delete',
@@ -151,6 +153,7 @@ export const BUS_API_EVENT = {
151153
PROJECT_ATTACHMENT_ADDED: 'project.action.create',
152154
PROJECT_ATTACHMENT_REMOVED: 'project.action.delete',
153155
PROJECT_ATTACHMENT_UPDATED: 'project.action.update',
156+
PROJECT_ATTACHMENT_SCAN_RESULT: 'avscan.projects.assets.result',
154157

155158
// When phase is added/updated/deleted from the project,
156159
// When product is added/deleted from a phase

src/events/attachments/index.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Event handlers for attachment create, and av scan result
3+
*/
4+
import Joi from 'joi';
5+
import config from 'config';
6+
import util from '../../util';
7+
import { BUS_API_EVENT } from '../../constants';
8+
import { createEvent } from '../../services/busApi';
9+
10+
/**
11+
* Payload for new unified BUS events like `avscan.projects.assets.result` with `resource=attachment`
12+
*/
13+
const attachmentScanResultPayloadSchema = Joi.object().keys({
14+
url: Joi.string().required(),
15+
fileName: Joi.string().required(),
16+
isInfected: Joi.boolean().required(),
17+
}).unknown(true).required();
18+
19+
/**
20+
* Updates project activity fields. throws exceptions in case of error
21+
* @param {Object} app Application object used to interact with RMQ service
22+
* @param {String} topic Kafka topic
23+
* @param {Object} payload Message payload
24+
* @return {Promise} Promise
25+
*/
26+
async function attachmentScanResultKafkaHandler(app, topic, payload) {
27+
// Validate payload
28+
const result = Joi.validate(payload, attachmentScanResultPayloadSchema);
29+
if (result.error) {
30+
throw new Error(result.error);
31+
}
32+
const sourceBucket = config.get('attachmentsDMZS3Bucket');
33+
// if the attachment is infected, move it to the quarantine s3 bucket, if not move it to the clean s3 bucket
34+
if (payload.isInfected) {
35+
// move to quarantine
36+
const destBucket = config.get('attachmentsQuarantineS3Bucket');
37+
util.s3FileTransfer({ log: app.logger }, sourceBucket, payload.path, destBucket, payload.path);
38+
app.logger.debug(`Attachment ${payload.fileName} is infected, moving to quarantine`);
39+
} else {
40+
// move to clean
41+
const destBucket = config.get('attachmentsS3Bucket');
42+
util.s3FileTransfer({ log: app.logger }, sourceBucket, payload.path, destBucket, payload.path);
43+
}
44+
}
45+
46+
/**
47+
* Payload for new unified BUS events like `avscan.action.scan` with `resource=attachment`
48+
*/
49+
const attachmentPayloadSchema = Joi.object().keys({
50+
path: Joi.string().required(),
51+
}).unknown(true).required();
52+
53+
/**
54+
* Attachment Created BUS API event handler.
55+
* - requests av scan by posting a kafka message to `avscan.action.scan` topic
56+
* - throws exceptions in case of error
57+
*
58+
* @param {Object} app Application object
59+
* @param {String} topic Kafka topic
60+
* @param {Object} payload Message payload
61+
* @return {Promise} Promise
62+
*/
63+
async function attachmentCreatedKafkaHandler(app, topic, payload) {
64+
// Validate payload
65+
const result = Joi.validate(payload, attachmentPayloadSchema);
66+
if (result.error) {
67+
throw new Error(result.error);
68+
}
69+
// Construct s3 url
70+
const avScanPayload = {
71+
url: `https://${config.get('attachmentsDMZS3Bucket')}.s3.amazonaws.com/${encodeURIComponent(payload.path)}`,
72+
path: payload.path,
73+
fileName: payload.path.split('/').pop(),
74+
moveFile: false,
75+
callbackOption: 'kafka',
76+
callbackKafkaTopic: BUS_API_EVENT.PROJECT_ATTACHMENT_SCAN_RESULT,
77+
};
78+
await createEvent(
79+
BUS_API_EVENT.AV_SCAN_REQUEST,
80+
avScanPayload,
81+
app.logger,
82+
);
83+
}
84+
85+
module.exports = {
86+
attachmentScanResultKafkaHandler,
87+
attachmentCreatedKafkaHandler,
88+
};

src/events/kafkaHandlers.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ import {
1717
} from './projectPhases';
1818
import { timelineAdjustedKafkaHandler } from './timelines';
1919
import { milestoneUpdatedKafkaHandler } from './milestones';
20+
import {
21+
attachmentScanResultKafkaHandler,
22+
attachmentCreatedKafkaHandler,
23+
} from './attachments';
2024

2125
const kafkaHandlers = {
2226
/**
@@ -37,6 +41,9 @@ const kafkaHandlers = {
3741
// Events coming from timeline/milestones (considering it as a separate module/service in future)
3842
[CONNECT_NOTIFICATION_EVENT.MILESTONE_TRANSITION_COMPLETED]: milestoneUpdatedKafkaHandler,
3943
[CONNECT_NOTIFICATION_EVENT.TIMELINE_ADJUSTED]: timelineAdjustedKafkaHandler,
44+
45+
// Events coming from attachments
46+
[BUS_API_EVENT.PROJECT_ATTACHMENT_SCAN_RESULT]: attachmentScanResultKafkaHandler,
4047
};
4148

4249
/**
@@ -95,6 +102,10 @@ registerKafkaHandler(
95102
RESOURCES.PHASE,
96103
projectPhaseRemovedKafkaHandler,
97104
);
98-
105+
registerKafkaHandler(
106+
BUS_API_EVENT.PROJECT_ATTACHMENT_ADDED,
107+
RESOURCES.ATTACHMENT,
108+
attachmentCreatedKafkaHandler,
109+
);
99110

100111
export default kafkaHandlers;

src/routes/attachments/create.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ module.exports = [
7070

7171
const sourceBucket = data.s3Bucket;
7272
const sourceKey = data.path;
73-
const destBucket = config.get('attachmentsS3Bucket');
73+
const destBucket = config.get('attachmentsDMZS3Bucket');
7474
const destKey = path;
7575

7676
if (data.type === ATTACHMENT_TYPES.LINK) {

src/routes/projects/create.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import util from '../../util';
1414
import { PERMISSION } from '../../permissions/constants';
1515

1616
const traverse = require('traverse');
17+
const xss = require('xss');
1718

1819
/**
1920
* API to handle creating a new project.
@@ -418,7 +419,11 @@ module.exports = [
418419
// keep the raw '&&' string in conditions string in estimation
419420
const isEstimationCondition =
420421
(this.path.length === 3) && (this.path[0] === 'estimation') && (this.key === 'conditions');
421-
if (this.isLeaf && typeof x === 'string' && (!isEstimationCondition)) this.update(req.sanitize(x));
422+
// if (this.isLeaf && typeof x === 'string' && (!isEstimationCondition)) this.update(req.sanitize(x));
423+
if (this.isLeaf && typeof x === 'string' && !isEstimationCondition) {
424+
const sanitizedData = xss(x);
425+
this.update(sanitizedData);
426+
}
422427
});
423428
// override values
424429
_.assign(project, {

src/routes/projects/update.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import util from '../../util';
1515
import { PERMISSION } from '../../permissions/constants';
1616

1717
const traverse = require('traverse');
18+
const xss = require('xss');
1819

1920
/**
2021
* API to handle updating a project.
@@ -190,7 +191,11 @@ module.exports = [
190191
// prune any fields that cannot be updated directly
191192
updatedProps = _.omit(updatedProps, ['createdBy', 'createdAt', 'updatedBy', 'updatedAt', 'id']);
192193
traverse(updatedProps).forEach(function (x) { // eslint-disable-line func-names
193-
if (x && this.isLeaf && typeof x === 'string') this.update(req.sanitize(x));
194+
// if (x && this.isLeaf && typeof x === 'string') this.update(req.sanitize(x));
195+
if (x && this.isLeaf && typeof x === 'string') {
196+
const sanitizedData = xss(x);
197+
this.update(sanitizedData);
198+
}
194199
});
195200
let previousValue;
196201
models.sequelize.transaction(() => models.Project.findOne({

0 commit comments

Comments
 (0)