0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-03-11 02:12:21 -05:00

Created DB Backup integration (#10974)

* Simplified db controller permissions options

The existing objects were confusing because they did the same thing as
setting permissions to true, but gave the impressions that something
special was happening/required.

* Added DB Backup Integration Role

This will allow us to assign certain api_keys this role, in order to
automate db backups

* Allowed admin api_keys to have configurable roles

This will allow keys for the admin api to do customised things such as db export

* Added ghost-backup integration to fixtures

* Added migrations for DB Backup Integration and role
This commit is contained in:
Fabien O'Carroll 2019-08-02 17:28:02 +08:00 committed by GitHub
parent 5f9f5ea0d5
commit 21427ad73f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 188 additions and 20 deletions

View file

@ -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:

View file

@ -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);
};

View file

@ -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);
};

View file

@ -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",

View file

@ -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'));
});

View file

@ -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();
});

View file

@ -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);

View file

@ -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