0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-10 23:36:14 -05:00
ghost/core/server/data/exporter/index.js
Naz aaa54c603c Refactored exporter to use "allowlist" table filtering
refs https://github.com/TryGhost/Team/issues/555

- Previous blocklist approach was resulting in adding every single new table into an export automatically. Which creates possibility to leak sensitive data if not used porperly. Allowlist approach gives better control over what is exported, makes this information explicit, and version-control friendlier
2021-03-25 16:46:56 +13:00

152 lines
4.2 KiB
JavaScript

const _ = require('lodash');
const Promise = require('bluebird');
const db = require('../../data/db');
const commands = require('../schema').commands;
const ghostVersion = require('../../lib/ghost-version');
const {i18n} = require('../../lib/common');
const logging = require('../../../shared/logging');
const errors = require('@tryghost/errors');
const security = require('@tryghost/security');
const models = require('../../models');
// NOTE: these tables can be optionally included to have full db-like export
const BACKUP_TABLES = [
'sessions',
'mobiledoc_revisions',
'email_batches',
'email_recipients',
'members_payment_events',
'members_login_events',
'members_email_change_events',
'members_status_events',
'members_paid_subscription_events',
'members_subscribe_events'
];
// NOTE: exposing only tables which are going to be included in a "default" export file
const TABLES_ALLOWLIST = [
'actions',
'api_keys',
'brute',
'emails',
'integrations',
'invites',
'labels',
'members',
'members_labels',
'members_stripe_customers',
'members_stripe_customers_subscriptions',
'migrations',
'migrations_lock',
'permissions',
'permissions_roles',
'permissions_users',
'posts',
'posts_authors',
'posts_meta',
'posts_tags',
'roles',
'roles_users',
'settings',
'snippets',
'tags',
'tokens',
'users',
'webhooks'
];
// NOTE: these are settings keys which should never end up in the export file
const SETTING_KEYS_BLOCKLIST = [
'stripe_connect_publishable_key',
'stripe_connect_secret_key',
'stripe_connect_account_id',
'stripe_secret_key',
'stripe_publishable_key',
'members_stripe_webhook_id',
'members_stripe_webhook_secret'
];
const modelOptions = {context: {internal: true}};
const exportFileName = async function exportFileName(options) {
const datetime = require('moment')().format('YYYY-MM-DD-HH-mm-ss');
let title = '';
options = options || {};
// custom filename
if (options.filename) {
return options.filename + '.json';
}
try {
const settingsTitle = await models.Settings.findOne({key: 'title'}, _.merge({}, modelOptions, _.pick(options, 'transacting')));
if (settingsTitle) {
title = security.string.safe(settingsTitle.get('value')) + '.';
}
return title + 'ghost.' + datetime + '.json';
} catch (err) {
logging.error(new errors.GhostError({err: err}));
return 'ghost.' + datetime + '.json';
}
};
const exportTable = function exportTable(tableName, options) {
if (TABLES_ALLOWLIST.includes(tableName) ||
(options.include && _.isArray(options.include) && options.include.indexOf(tableName) !== -1)) {
const query = (options.transacting || db.knex)(tableName);
return query.select();
}
};
const getSettingsTableData = function getSettingsTableData(settingsData) {
return settingsData && settingsData.filter((setting) => {
return !SETTING_KEYS_BLOCKLIST.includes(setting.key);
});
};
const doExport = async function doExport(options) {
options = options || {include: []};
try {
const tables = await commands.getTables(options.transacting);
const tableData = await Promise.mapSeries(tables, function (tableName) {
return exportTable(tableName, options);
});
const exportData = {
meta: {
exported_on: new Date().getTime(),
version: ghostVersion.full
},
data: {
// Filled below
}
};
tables.forEach((name, i) => {
if (name === 'settings') {
exportData.data[name] = getSettingsTableData(tableData[i]);
} else {
exportData.data[name] = tableData[i];
}
});
return exportData;
} catch (err) {
throw new errors.DataExportError({
err: err,
context: i18n.t('errors.data.export.errorExportingData')
});
}
};
module.exports = {
doExport: doExport,
fileName: exportFileName,
BACKUP_TABLES: BACKUP_TABLES
};