diff --git a/core/server/api/v2/db.js b/core/server/api/v2/db.js index 96fc71a8f7..7f7323a914 100644 --- a/core/server/api/v2/db.js +++ b/core/server/api/v2/db.js @@ -46,9 +46,7 @@ module.exports = { value: () => (exporter.fileName()) } }, - permissions: { - method: 'exportContent' - }, + permissions: true, query(frame) { return Promise.resolve() .then(() => exporter.doExport({include: frame.options.withRelated})) @@ -69,9 +67,7 @@ module.exports = { } } }, - permissions: { - method: 'importContent' - }, + permissions: true, query(frame) { return importer.importFromFile(frame.file, {include: frame.options.withRelated}); } @@ -79,9 +75,7 @@ module.exports = { deleteAllContent: { statusCode: 204, - permissions: { - method: 'deleteAllContent' - }, + permissions: true, query() { /** * @NOTE: diff --git a/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js b/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js new file mode 100644 index 0000000000..ac8d796ac4 --- /dev/null +++ b/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js @@ -0,0 +1,84 @@ +const logging = require('../../../../lib/common/logging'); +const merge = require('lodash/merge'); +const models = require('../../../../models'); +const utils = require('../../../schema/fixtures/utils'); + +const _private = {}; + +_private.printResult = function printResult(result, message) { + if (result.done === result.expected) { + logging.info(message); + } else { + logging.warn(`(${result.done}/${result.expected}) ${message}`); + } +}; + +_private.addApiKeyRole = (options) => { + const message = 'Adding "DB Backup Integration" role to roles table'; + const apiKeyRole = utils.findModelFixtureEntry('Role', {name: 'DB Backup Integration'}); + + return models.Role.findOne({name: apiKeyRole.name}, options) + .then((role) => { + if (!role) { + return utils.addFixturesForModel({ + name: 'Role', + entries: [apiKeyRole] + }, options).then(result => _private.printResult(result, message)); + } + + logging.warn(message); + }); +}; + +_private.addApiKeyPermissions = (options) => { + const message = 'Adding permissions for the "DB Backup Integration" role'; + const relations = utils.findRelationFixture('Role', 'Permission'); + + return utils.addFixturesForRelation({ + from: relations.from, + to: relations.to, + entries: { + 'DB Backup Integration': relations.entries['DB Backup Integration'] + } + }, options).then(result => _private.printResult(result, message)); +}; + +_private.removeApiKeyPermissionsAndRole = (options) => { + const message = 'Rollback: Removing "DB Backup Integration" role and permissions'; + + return models.Role.findOne({name: 'DB Backup Integration'}, options) + .then((role) => { + if (!role) { + logging.warn(message); + return; + } + + return role.destroy().then(() => { + logging.info(message); + }); + }); +}; + +module.exports.config = { + transaction: true +}; + +module.exports.up = (options) => { + const localOptions = merge({ + context: {internal: true}, + migrating: true + }, options); + + return _private.addApiKeyRole(localOptions) + .then(() => _private.addApiKeyPermissions(localOptions)); +}; + +module.exports.down = (options) => { + const localOptions = merge({ + context: {internal: true}, + migrating: true + }, options); + + return _private.removeApiKeyPermissionsAndRole(localOptions); +}; + diff --git a/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js b/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js new file mode 100644 index 0000000000..ffd53ddb2d --- /dev/null +++ b/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js @@ -0,0 +1,70 @@ +const logging = require('../../../../lib/common/logging'); +const merge = require('lodash/merge'); +const models = require('../../../../models'); +const utils = require('../../../schema/fixtures/utils'); + +const _private = {}; + +_private.printResult = function printResult(result, message) { + if (result.done === result.expected) { + logging.info(message); + } else { + logging.warn(`(${result.done}/${result.expected}) ${message}`); + } +}; + +_private.addGhostBackupIntegration = (options) => { + const message = 'Adding "Ghost Backup DB" integration'; + const fixtureIntegration = utils.findModelFixtureEntry('Integration', {slug: 'ghost-backup'}); + + return models.Integration.findOne({slug: fixtureIntegration.slug}, options) + .then((integration) => { + if (!integration) { + return utils.addFixturesForModel({ + name: 'Integration', + entries: [fixtureIntegration] + }, options).then(result => _private.printResult(result, message)); + } + + logging.warn(message); + }); +}; + +_private.removeGhostBackupIntegration = (options) => { + const message = 'Rollback: Removing "Ghost Backup DB" integration'; + + return models.Integration.findOne({slug: 'ghost-backup'}, options) + .then((integration) => { + if (!integration) { + logging.warn(message); + return; + } + + return integration.destroy().then(() => { + logging.info(message); + }); + }); +}; + +module.exports.config = { + transaction: true +}; + +module.exports.up = (options) => { + const localOptions = merge({ + context: {internal: true}, + migrating: true + }, options); + + return _private.addGhostBackupIntegration(localOptions); +}; + +module.exports.down = (options) => { + const localOptions = merge({ + context: {internal: true}, + migrating: true + }, options); + + return _private.removeGhostBackupIntegration(localOptions); +}; + diff --git a/core/server/data/schema/fixtures/fixtures.json b/core/server/data/schema/fixtures/fixtures.json index eff87a9cf6..5ac05d6894 100644 --- a/core/server/data/schema/fixtures/fixtures.json +++ b/core/server/data/schema/fixtures/fixtures.json @@ -66,6 +66,10 @@ { "name": "Admin Integration", "description": "External Apps" + }, + { + "name": "DB Backup Integration", + "description": "Internal DB Backup Client" } ] }, @@ -568,6 +572,13 @@ "description": "Built-in Zapier integration", "type": "builtin", "api_keys": [{"type": "admin"}] + }, + { + "slug": "ghost-backup", + "name": "Ghost Backup", + "description": "Internal DB Backup integration", + "type": "internal", + "api_keys": [{"type": "admin", "role": "DB Backup Integration"}] } ] } @@ -605,6 +616,9 @@ "action": "all", "member": "all" }, + "DB Backup Integration": { + "db": "all" + }, "Admin Integration": { "mail": "all", "notification": "all", diff --git a/core/server/models/api-key.js b/core/server/models/api-key.js index 0fa6ea0ea2..ee5e5b340b 100644 --- a/core/server/models/api-key.js +++ b/core/server/models/api-key.js @@ -1,3 +1,4 @@ +const omit = require('lodash/omit'); const crypto = require('crypto'); const ghostBookshelf = require('./base'); const {Role} = require('./role'); @@ -44,6 +45,10 @@ const ApiKey = ghostBookshelf.Model.extend({ return this.belongsTo('Integration'); }, + format(attrs) { + return omit(attrs, 'role'); + }, + onSaving(model, attrs, options) { ghostBookshelf.Model.prototype.onSaving.apply(this, arguments); @@ -52,7 +57,7 @@ const ApiKey = ghostBookshelf.Model.extend({ // - content key = no role if (this.hasChanged('type') || this.hasChanged('role_id')) { if (this.get('type') === 'admin') { - return Role.findOne({name: 'Admin Integration'}, Object.assign({}, options, {columns: ['id']})) + return Role.findOne({name: attrs.role || 'Admin Integration'}, Object.assign({}, options, {columns: ['id']})) .then((role) => { this.set('role_id', role.get('id')); }); diff --git a/core/test/acceptance/old/admin/roles_spec.js b/core/test/acceptance/old/admin/roles_spec.js index f09b9ff7ab..4083bf9c65 100644 --- a/core/test/acceptance/old/admin/roles_spec.js +++ b/core/test/acceptance/old/admin/roles_spec.js @@ -35,13 +35,14 @@ describe('Roles API', function () { should.exist(response); should.exist(response.roles); localUtils.API.checkResponse(response, 'roles'); - response.roles.should.have.length(6); + response.roles.should.have.length(7); localUtils.API.checkResponse(response.roles[0], 'role'); localUtils.API.checkResponse(response.roles[1], 'role'); localUtils.API.checkResponse(response.roles[2], 'role'); localUtils.API.checkResponse(response.roles[3], 'role'); localUtils.API.checkResponse(response.roles[4], 'role'); localUtils.API.checkResponse(response.roles[5], 'role'); + localUtils.API.checkResponse(response.roles[6], 'role'); done(); }); diff --git a/core/test/unit/data/schema/fixtures/utils_spec.js b/core/test/unit/data/schema/fixtures/utils_spec.js index 2f455f6843..481c6d3102 100644 --- a/core/test/unit/data/schema/fixtures/utils_spec.js +++ b/core/test/unit/data/schema/fixtures/utils_spec.js @@ -150,19 +150,19 @@ describe('Migration Fixture Utils', function () { fixtureUtils.addFixturesForRelation(fixtures.relations[0]).then(function (result) { should.exist(result); result.should.be.an.Object(); - result.should.have.property('expected', 64); - result.should.have.property('done', 64); + result.should.have.property('expected', 65); + result.should.have.property('done', 65); // Permissions & Roles permsAllStub.calledOnce.should.be.true(); rolesAllStub.calledOnce.should.be.true(); - dataMethodStub.filter.callCount.should.eql(64); - dataMethodStub.find.callCount.should.eql(5); - baseUtilAttachStub.callCount.should.eql(64); + dataMethodStub.filter.callCount.should.eql(65); + dataMethodStub.find.callCount.should.eql(6); + baseUtilAttachStub.callCount.should.eql(65); - fromItem.related.callCount.should.eql(64); - fromItem.findWhere.callCount.should.eql(64); - toItem[0].get.callCount.should.eql(128); + fromItem.related.callCount.should.eql(65); + fromItem.findWhere.callCount.should.eql(65); + toItem[0].get.callCount.should.eql(130); done(); }).catch(done); diff --git a/core/test/unit/data/schema/integrity_spec.js b/core/test/unit/data/schema/integrity_spec.js index 056122e809..902924bfa5 100644 --- a/core/test/unit/data/schema/integrity_spec.js +++ b/core/test/unit/data/schema/integrity_spec.js @@ -20,7 +20,7 @@ var should = require('should'), describe('DB version integrity', function () { // Only these variables should need updating const currentSchemaHash = 'fda0398e93a74b2dc435cb4c026679ba'; - const currentFixturesHash = '28e4057d63ce22613a2d03f520872548'; + const currentFixturesHash = '06771caf35c48fd69cb209a988237c33'; // If this test is failing, then it is likely a change has been made that requires a DB version bump, // and the values above will need updating as confirmation