diff --git a/.env.example b/.env.example index ad0891651a..3ad6fe79aa 100644 --- a/.env.example +++ b/.env.example @@ -193,4 +193,5 @@ FS_REPO=local ######## # Location of virus scanning binary (ClamAV - https://www.clamav.net/) -#CLAMSCAN_BINARY=/usr/bin/clamscan +#CLAMDSCAN_BINARY=/usr/bin/clamdscan +#CLAMDSCAN_CONF=/etc/clamav/clamd.conf diff --git a/lib/helpers/getDateFromMongoID.js b/lib/helpers/getDateFromMongoID.js new file mode 100644 index 0000000000..0d62447e22 --- /dev/null +++ b/lib/helpers/getDateFromMongoID.js @@ -0,0 +1,18 @@ +import isString from 'lodash/isString'; + +export default (_id) => { + if (!isString(_id) || _id.length !== 24) { + // not a mongo id + throw Error('Not a Mongo ID'); + } + const timehex = _id.substring(0, 8); + + // convert to a number... base 16 + const secondsSinceEpoch = parseInt(timehex, 16); + if (isNaN(secondsSinceEpoch)) { + throw Error('Invalid date in Mongo ID'); + } + + // convert to milliseconds, and create a new date + return new Date(secondsSinceEpoch * 1000); +}; diff --git a/lib/helpers/tests/getDateFromMongoID-test.js b/lib/helpers/tests/getDateFromMongoID-test.js new file mode 100644 index 0000000000..2b1e6be9ea --- /dev/null +++ b/lib/helpers/tests/getDateFromMongoID-test.js @@ -0,0 +1,29 @@ +import { expect } from 'chai'; +import getDateFromMongoID from '../getDateFromMongoID'; + +const _id = '5ad52af00000000000000000'; +describe('getDateFromMongoID', () => { + it('should return date for Mongo ID as string', () => { + const date = getDateFromMongoID(_id); + const expectedDate = new Date('2018-04-16T23:00:00'); + expect(date.getTime()).to.equal(expectedDate.getTime()); + }); + + it('should error when _id is not a string', () => { + expect(getDateFromMongoID.bind(null, 123)).to.throw(Error); + expect(getDateFromMongoID.bind(null, false)).to.throw(Error); + expect(getDateFromMongoID.bind(null, undefined)).to.throw(Error); + expect(getDateFromMongoID.bind(null, null)).to.throw(Error); + expect(getDateFromMongoID.bind(null, {})).to.throw(Error); + expect(getDateFromMongoID.bind(null, [])).to.throw(Error); + }); + + it('should error when _id is not 24 chars', () => { + expect(getDateFromMongoID.bind(null, 'abc')).to.throw(Error); + expect(getDateFromMongoID.bind(null, `${_id}0`)).to.throw(Error); + }); + + it('should error when _id does not contain a valid hex date', () => { + expect(getDateFromMongoID.bind(null, 'Zad52af00000000000000000')).to.throw(Error); + }); +}); diff --git a/lib/models/statementForwarding.js b/lib/models/statementForwarding.js index b78826f220..6a2dfacb04 100644 --- a/lib/models/statementForwarding.js +++ b/lib/models/statementForwarding.js @@ -166,7 +166,7 @@ const schema = new mongoose.Schema({ query: { type: String, validate: { - validator: value => { + validator: (value) => { try { JSON.parse(value); } catch (err) { @@ -174,7 +174,7 @@ const schema = new mongoose.Schema({ } return true; }, - message: "Invalid query" + message: 'Invalid query' } }, isPublic: { type: Boolean, default: false } diff --git a/lib/services/files/clamscan.js b/lib/services/files/clamscan.js index dfa8aac75e..23fb58ba7d 100644 --- a/lib/services/files/clamscan.js +++ b/lib/services/files/clamscan.js @@ -1,32 +1,35 @@ import Promise from 'bluebird'; import logger from 'lib/logger'; +import defaultTo from 'lodash/defaultTo'; -const scanError = err => ( - new Error(`Something went wrong when scanning the file. ${err.message}`) -); const virusError = () => ( new Error('This file has not passed the virus scan and will be deleted.') ); export default filePath => new Promise((resolve, reject) => { - if (!process.env.CLAMSCAN_BINARY) { - logger.warn('CLAMSCAN NOT INSTALLED, SEE DOCS FOR FURTHER INFORMATION.'); + if (!process.env.CLAMDSCAN_BINARY) { + logger.warn('CLAMDSCAN NOT INSTALLED, SEE DOCS FOR FURTHER INFORMATION.'); return resolve(filePath); } try { const clam = require('clamscan')({ remove_infected: true, - clamscan: { - path: process.env.CLAMSCAN_BINARY, + clamdscan: { + path: process.env.CLAMDSCAN_BINARY, + config_file: defaultTo(process.env.CLAMDSCAN_CONF, '/etc/clamav/clamd.conf') }, - preference: 'clamscan', + preference: 'clamdscan', }); clam.is_infected(filePath, (err, file, isInfected) => { - if (err) return reject(scanError(err)); + if (err) { + logger.warn('ERROR SCANNING FILE WITH CLAMD - ', err); + return resolve(filePath); + } if (isInfected) return reject(virusError()); return resolve(filePath); }); } catch (err) { - return reject(scanError(err)); + logger.warn('ERROR SCANNING FILE WITH CLAMD - ', err); + return resolve(filePath); } }); diff --git a/lib/services/importPersonas/getCsvHeaders.js b/lib/services/importPersonas/getCsvHeaders.js index a28f038972..79ab19df2b 100644 --- a/lib/services/importPersonas/getCsvHeaders.js +++ b/lib/services/importPersonas/getCsvHeaders.js @@ -1,11 +1,10 @@ -import * as fileService from 'lib/services/files'; import csv from 'fast-csv'; import { isUndefined, uniq } from 'lodash'; import EmptyCsvError from 'lib/errors/EmptyCsvError'; import DuplicateCsvHeadersError from 'lib/errors/DuplicateCsvHeadersError'; -const getCsvHeaders = async (handle) => { - const csvStream = csv.parse({ +const getCsvHeaders = async (fileStream) => { + const csvStreamHandler = csv.parse({ headers: false, quoteHeaders: true }); @@ -13,17 +12,17 @@ const getCsvHeaders = async (handle) => { // read the first row. let headers; - csvStream.on('data', (data) => { + csvStreamHandler.on('data', (data) => { // we're just interested in the first one. if (isUndefined(headers)) headers = data; }); const csvPromise = new Promise((resolve, reject) => { - csvStream.on('error', reject); - csvStream.on('finish', resolve); + csvStreamHandler.on('error', reject); + csvStreamHandler.on('finish', resolve); }); - fileService.downloadToStream(handle)(csvStream); + fileStream.pipe(csvStreamHandler); await csvPromise; diff --git a/lib/services/importPersonas/persistPersonas.js b/lib/services/importPersonas/persistPersonas.js index 69a01f99a7..5778df50b7 100644 --- a/lib/services/importPersonas/persistPersonas.js +++ b/lib/services/importPersonas/persistPersonas.js @@ -2,7 +2,7 @@ import fs from 'fs'; import clamscan from 'lib/services/files/clamscan'; import { uploadFromStream } from 'lib/services/files/storage'; -export const PERSONAS_CSV_PATH = '/personasCsvs'; +export const PERSONAS_CSV_PATH = 'personasCsvs'; export default async ({ file, diff --git a/lib/services/importPersonas/uploadPersonas.js b/lib/services/importPersonas/uploadPersonas.js index e08da78e2b..456f698116 100644 --- a/lib/services/importPersonas/uploadPersonas.js +++ b/lib/services/importPersonas/uploadPersonas.js @@ -1,3 +1,4 @@ +import fs from 'fs'; import persistPersonas from 'lib/services/importPersonas/persistPersonas'; import PersonasImport from 'lib/models/personasImport'; import { STAGE_CONFIGURE_FIELDS } from 'lib/constants/personasImport'; @@ -29,7 +30,10 @@ const uploadPersonas = async ({ id }); - const csvHeaders = await getCsvHeaders(handle); + // instead of going back out to s3, use the local file we already have! + const filePath = file.path; + const fileStream = fs.createReadStream(filePath); + const csvHeaders = await getCsvHeaders(fileStream); const structure = await getStructure({ csvHeaders, diff --git a/ui/src/containers/Owner/CreatedAt.js b/ui/src/containers/Owner/CreatedAt.js index 5ddf2e284e..2e30382717 100644 --- a/ui/src/containers/Owner/CreatedAt.js +++ b/ui/src/containers/Owner/CreatedAt.js @@ -1,4 +1,6 @@ import moment from 'moment'; -export default createdAt => - `Created ${moment(createdAt).fromNow()} - ${moment(createdAt).format('YYYY-MM-DD HH:mm:ss')}`; +export default (createdAt) => { + if (!createdAt) return ''; + return `Created ${moment(createdAt).fromNow()} - ${moment(createdAt).format('YYYY-MM-DD HH:mm:ss')}`; +}; diff --git a/ui/src/containers/Owner/index.js b/ui/src/containers/Owner/index.js index cfd0bdca9c..fedaa690a2 100644 --- a/ui/src/containers/Owner/index.js +++ b/ui/src/containers/Owner/index.js @@ -1,24 +1,41 @@ import React from 'react'; +import getDateFromMongoID from 'lib/helpers/getDateFromMongoID'; import { withProps, compose } from 'recompose'; import { withModel } from 'ui/utils/hocs'; import createdAtFormatter from './CreatedAt'; +const getCreatedDate = (createdAt, _id) => { + if (!createdAt && _id) { + try { + return getDateFromMongoID(_id); + } catch (err) { + // error getting date from id (might not be a valid mongo id) + return; + } + } + return createdAt; +}; + const Creator = compose( withProps(({ model }) => ({ schema: 'user', id: model.get('owner') })), withModel -)(({ model, createdAt }) => { +)(({ model, date }) => { const email = model.get('email', false); const name = model.get('name', false); return (
Made by { email ? {name || email} : deleted user }
-
{ createdAtFormatter(createdAt) }
+
{ date }
); }); -export default ({ model }) => ( - model.has('owner') - ? - :
{ createdAtFormatter(model.get('createdAt')) }
-); +export default ({ model }) => { + const date = getCreatedDate(model.get('createdAt', false), model.get('_id')); + const formattedDate = createdAtFormatter(date); + return ( + model.has('owner') + ? + :
{ formattedDate }
+ ); +};