Skip to content

Commit

Permalink
feat(doctor): content folder permission checks (#610)
Browse files Browse the repository at this point in the history
refs #47

- adds a new task to `ghost doctor startup` which runs on `ghost start`
- skips this task if
	- ghost user is not setup or
	- ghost user doesn't own the content folder
- checks the permissions inside the content folder and fails if a file or directory is not owned by ghost
- logs the relevant files or directories
- adds tests
  • Loading branch information
aileen authored and acburdine committed Feb 4, 2018
1 parent 76829e9 commit 0075e77
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 1 deletion.
43 changes: 43 additions & 0 deletions lib/commands/doctor/checks/content-folder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use strict';
const execa = require('execa');
const path = require('path');
const Promise = require('bluebird');
const chalk = require('chalk');

const errors = require('../../../errors');
const shouldUseGhostUser = require('../../../utils/use-ghost-user');

function contentFolderPermissions() {
return execa.shell('find ./content ! -group ghost ! -user ghost').then((result) => {
let errMsg;

if (!result.stdout) {
return Promise.resolve();
}

const resultDirs = result.stdout.split('\n');
const dirWording = resultDirs.length > 1 ? 'some directories or files' : 'a directory or file';

errMsg = `Your content folder contains ${dirWording} with incorrect permissions:\n`;

resultDirs.forEach((folder) => {
errMsg += `- ${folder}\n`
});

errMsg += `Run ${chalk.blue('sudo chown -R ghost:ghost ./content')} and try again.`

return Promise.reject(new errors.SystemError(errMsg));
}).catch((error) => {
if (error instanceof errors.SystemError) {
return Promise.reject(error);
}
return Promise.reject(new errors.ProcessError(error));
});
}

module.exports = {
title: 'Content folder permissions',
enabled: () => shouldUseGhostUser(path.join(process.cwd(), 'content')),
task: contentFolderPermissions,
category: ['start']
}
4 changes: 3 additions & 1 deletion lib/commands/doctor/checks/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ const folderPermissions = require('./folder-permissions');
const systemStack = require('./system-stack');
const mysqlCheck = require('./mysql');
const validateConfig = require('./validate-config');
const contentFolderPermissions = require('./content-folder');

module.exports = [
nodeVersion,
folderPermissions,
systemStack,
mysqlCheck,
validateConfig
validateConfig,
contentFolderPermissions
];
80 changes: 80 additions & 0 deletions test/unit/commands/doctor/checks/content-folder-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict';
const expect = require('chai').expect;
const sinon = require('sinon');

const execa = require('execa');
const errors = require('../../../../../lib/errors');

const contentFolderPermissions = require('../../../../../lib/commands/doctor/checks/content-folder');

describe('Unit: Doctor Checks > Content folder permissions', function () {
const sandbox = sinon.sandbox.create();
const shouldUseGhostUserStub = sinon.stub();

afterEach(() => {
sandbox.restore();
});

it('skips when content folder is not owned by ghost', function () {
shouldUseGhostUserStub.returns(false);
const execaStub = sandbox.stub(execa, 'shell').resolves();

expect(contentFolderPermissions).to.exist;
expect(contentFolderPermissions.enabled(), 'skips if no Ghost user should be used').to.be.false;
expect(execaStub.called).to.be.false;
});

it('rejects with error if folders have incorrect permissions', function () {
const execaStub = sandbox.stub(execa, 'shell').resolves({stdout: './content/images\n./content/apps\n./content/themes'});

shouldUseGhostUserStub.returns(true);

return contentFolderPermissions.task({}).then(() => {
expect(false, 'error should have been thrown').to.be.true;
}).catch((error) => {
expect(error).to.be.an.instanceof(errors.SystemError);
expect(error.message).to.match(/Your content folder contains some directories or files with incorrect permissions:/);
expect(error.message).to.match(/- \.\/content\/images/);
expect(execaStub.called).to.be.true;
});
});

it('rejects with error if files have incorrect permissions', function () {
const execaStub = sandbox.stub(execa, 'shell').resolves({stdout: './content/images/test.jpg'});

shouldUseGhostUserStub.returns(true);

return contentFolderPermissions.task({}).then(() => {
expect(false, 'error should have been thrown').to.be.true;
}).catch((error) => {
expect(error).to.be.an.instanceof(errors.SystemError);
expect(error.message).to.match(/Your content folder contains a directory or file with incorrect permissions/);
expect(error.message).to.match(/- .\/content\/images\/test.jpg/);
expect(execaStub.called).to.be.true;
});
});

it('passes if all folders have the correct permissions', function () {
const execaStub = sandbox.stub(execa, 'shell').resolves({stdout: ''});

shouldUseGhostUserStub.returns(true);

return contentFolderPermissions.task({}).then(() => {
expect(execaStub.called).to.be.true;
});
});

it('rejects with error if execa command fails', function () {
const execaStub = sandbox.stub(execa, 'shell').rejects(new Error('oops, cmd could not be executed'));

shouldUseGhostUserStub.returns(true);

return contentFolderPermissions.task({}).then(() => {
expect(false, 'error should have been thrown').to.be.true;
}).catch((error) => {
expect(error).to.be.an.instanceof(errors.ProcessError);
expect(error.message).to.match(/oops, cmd could not be executed/);
expect(execaStub.called).to.be.true;
});
});
});

0 comments on commit 0075e77

Please sign in to comment.