-
-
Notifications
You must be signed in to change notification settings - Fork 227
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
3 changed files
with
126 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}); | ||
}); | ||
}); |