Skip to content

Commit 0075e77

Browse files
aileenacburdine
authored andcommitted
feat(doctor): content folder permission checks (#610)
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
1 parent 76829e9 commit 0075e77

File tree

3 files changed

+126
-1
lines changed

3 files changed

+126
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
const execa = require('execa');
3+
const path = require('path');
4+
const Promise = require('bluebird');
5+
const chalk = require('chalk');
6+
7+
const errors = require('../../../errors');
8+
const shouldUseGhostUser = require('../../../utils/use-ghost-user');
9+
10+
function contentFolderPermissions() {
11+
return execa.shell('find ./content ! -group ghost ! -user ghost').then((result) => {
12+
let errMsg;
13+
14+
if (!result.stdout) {
15+
return Promise.resolve();
16+
}
17+
18+
const resultDirs = result.stdout.split('\n');
19+
const dirWording = resultDirs.length > 1 ? 'some directories or files' : 'a directory or file';
20+
21+
errMsg = `Your content folder contains ${dirWording} with incorrect permissions:\n`;
22+
23+
resultDirs.forEach((folder) => {
24+
errMsg += `- ${folder}\n`
25+
});
26+
27+
errMsg += `Run ${chalk.blue('sudo chown -R ghost:ghost ./content')} and try again.`
28+
29+
return Promise.reject(new errors.SystemError(errMsg));
30+
}).catch((error) => {
31+
if (error instanceof errors.SystemError) {
32+
return Promise.reject(error);
33+
}
34+
return Promise.reject(new errors.ProcessError(error));
35+
});
36+
}
37+
38+
module.exports = {
39+
title: 'Content folder permissions',
40+
enabled: () => shouldUseGhostUser(path.join(process.cwd(), 'content')),
41+
task: contentFolderPermissions,
42+
category: ['start']
43+
}

lib/commands/doctor/checks/index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ const folderPermissions = require('./folder-permissions');
44
const systemStack = require('./system-stack');
55
const mysqlCheck = require('./mysql');
66
const validateConfig = require('./validate-config');
7+
const contentFolderPermissions = require('./content-folder');
78

89
module.exports = [
910
nodeVersion,
1011
folderPermissions,
1112
systemStack,
1213
mysqlCheck,
13-
validateConfig
14+
validateConfig,
15+
contentFolderPermissions
1416
];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
'use strict';
2+
const expect = require('chai').expect;
3+
const sinon = require('sinon');
4+
5+
const execa = require('execa');
6+
const errors = require('../../../../../lib/errors');
7+
8+
const contentFolderPermissions = require('../../../../../lib/commands/doctor/checks/content-folder');
9+
10+
describe('Unit: Doctor Checks > Content folder permissions', function () {
11+
const sandbox = sinon.sandbox.create();
12+
const shouldUseGhostUserStub = sinon.stub();
13+
14+
afterEach(() => {
15+
sandbox.restore();
16+
});
17+
18+
it('skips when content folder is not owned by ghost', function () {
19+
shouldUseGhostUserStub.returns(false);
20+
const execaStub = sandbox.stub(execa, 'shell').resolves();
21+
22+
expect(contentFolderPermissions).to.exist;
23+
expect(contentFolderPermissions.enabled(), 'skips if no Ghost user should be used').to.be.false;
24+
expect(execaStub.called).to.be.false;
25+
});
26+
27+
it('rejects with error if folders have incorrect permissions', function () {
28+
const execaStub = sandbox.stub(execa, 'shell').resolves({stdout: './content/images\n./content/apps\n./content/themes'});
29+
30+
shouldUseGhostUserStub.returns(true);
31+
32+
return contentFolderPermissions.task({}).then(() => {
33+
expect(false, 'error should have been thrown').to.be.true;
34+
}).catch((error) => {
35+
expect(error).to.be.an.instanceof(errors.SystemError);
36+
expect(error.message).to.match(/Your content folder contains some directories or files with incorrect permissions:/);
37+
expect(error.message).to.match(/- \.\/content\/images/);
38+
expect(execaStub.called).to.be.true;
39+
});
40+
});
41+
42+
it('rejects with error if files have incorrect permissions', function () {
43+
const execaStub = sandbox.stub(execa, 'shell').resolves({stdout: './content/images/test.jpg'});
44+
45+
shouldUseGhostUserStub.returns(true);
46+
47+
return contentFolderPermissions.task({}).then(() => {
48+
expect(false, 'error should have been thrown').to.be.true;
49+
}).catch((error) => {
50+
expect(error).to.be.an.instanceof(errors.SystemError);
51+
expect(error.message).to.match(/Your content folder contains a directory or file with incorrect permissions/);
52+
expect(error.message).to.match(/- .\/content\/images\/test.jpg/);
53+
expect(execaStub.called).to.be.true;
54+
});
55+
});
56+
57+
it('passes if all folders have the correct permissions', function () {
58+
const execaStub = sandbox.stub(execa, 'shell').resolves({stdout: ''});
59+
60+
shouldUseGhostUserStub.returns(true);
61+
62+
return contentFolderPermissions.task({}).then(() => {
63+
expect(execaStub.called).to.be.true;
64+
});
65+
});
66+
67+
it('rejects with error if execa command fails', function () {
68+
const execaStub = sandbox.stub(execa, 'shell').rejects(new Error('oops, cmd could not be executed'));
69+
70+
shouldUseGhostUserStub.returns(true);
71+
72+
return contentFolderPermissions.task({}).then(() => {
73+
expect(false, 'error should have been thrown').to.be.true;
74+
}).catch((error) => {
75+
expect(error).to.be.an.instanceof(errors.ProcessError);
76+
expect(error.message).to.match(/oops, cmd could not be executed/);
77+
expect(execaStub.called).to.be.true;
78+
});
79+
});
80+
});

0 commit comments

Comments
 (0)