🎨 deny auto switch (#8086)

* 🎨  deny auto switch

no issue

- deny auth switch after the blog was setup
- setup completed depends on the status of the user right now, see comments

* Updates from comments

- re-use statuses in user model
- update error message
This commit is contained in:
Katharina Irrgang 2017-03-02 20:50:58 +01:00 committed by Hannah Wolfe
parent 144544e83d
commit 9fafc38b79
8 changed files with 172 additions and 11 deletions

View File

@ -472,16 +472,10 @@ authentication = {
* @return {Promise}
*/
isSetup: function isSetup() {
var tasks,
validStatuses = ['active', 'warn-1', 'warn-2', 'warn-3', 'warn-4', 'locked'];
var tasks;
function checkSetupStatus() {
return models.User
.where('status', 'in', validStatuses)
.count('id')
.then(function (count) {
return !!count;
});
return models.User.isSetup();
}
function formatResponse(isSetup) {

View File

@ -3,6 +3,7 @@ var passport = require('./passport'),
authenticate = require('./authenticate'),
sync = require('./sync'),
oauth = require('./oauth'),
validation = require('./validation'),
ghostAuth = require('./ghost-auth');
exports.init = function (options) {
@ -15,6 +16,7 @@ exports.init = function (options) {
});
};
exports.validation = validation;
exports.oauth = oauth;
exports.authorize = authorize;
exports.authenticate = authenticate;

View File

@ -158,8 +158,21 @@ exports.init = function initPassport(options) {
passport.use(new ClientPasswordStrategy(authStrategies.clientPasswordStrategy));
passport.use(new BearerStrategy(authStrategies.bearerStrategy));
// CASE: use switches from password to ghost and back
// If we don't clean up the database, it can happen that the auth switch validation fails
if (authType !== 'ghost') {
return resolve({passport: passport.initialize()});
return models.Client.findOne({slug: 'ghost-auth'})
.then(function (client) {
if (!client) {
return;
}
return models.Client.destroy({id: client.id});
})
.then(function () {
resolve({passport: passport.initialize()});
})
.catch(reject);
}
var ghostOAuth2Strategy = new GhostOAuth2Strategy({

View File

@ -0,0 +1,31 @@
var Promise = require('bluebird'),
models = require('../models'),
errors = require('../errors');
/**
* If the setup is completed and...
* 1. the public client does exist, deny to switch to local
* 2. the public client does not exist, deny to switch to remote
*/
exports.switch = function validate(options) {
var authType = options.authType;
return models.User.isSetup()
.then(function (isSetup) {
if (!isSetup) {
return;
}
return models.Client.findOne({slug: 'ghost-auth'}, {columns: 'id'})
.then(function (client) {
if ((client && authType === 'password') || !client && authType === 'ghost') {
return Promise.reject(new errors.InternalServerError({
code: 'AUTH_SWITCH',
message: 'Switching the auth strategy is not allowed.',
context: 'Please reset your database and start from scratch.',
help: 'NODE_ENV=production|development knex-migrator reset && NODE_ENV=production|development knex-migrator init\n'
}));
}
});
});
};

View File

@ -72,7 +72,11 @@ function init() {
parentApp = require('./app')();
debug('Express Apps done');
}).then(function () {
return auth.validation.switch({
authType: config.get('auth:type')
});
}).then(function () {
// runs asynchronous
auth.init({
authType: config.get('auth:type'),

View File

@ -487,6 +487,19 @@ User = ghostBookshelf.Model.extend({
return self.edit.call(self, userData, options);
},
/**
* Right now the setup of the blog depends on the user status.
* @TODO: see https://github.com/TryGhost/Ghost/issues/8003
*/
isSetup: function isSetup() {
return this
.where('status', 'in', activeStates)
.count('id')
.then(function (count) {
return !!count;
});
},
permissible: function permissible(userModelOrId, action, context, loadedPermissions, hasUserPermission, hasAppPermission) {
var self = this,
userModel = userModelOrId,

View File

@ -52,6 +52,8 @@ describe('Ghost Passport', function () {
return Promise.resolve(client);
});
sandbox.stub(models.Client, 'destroy').returns(Promise.resolve());
sandbox.stub(models.Client, 'add', function () {
client = new models.Client(testUtils.DataGenerator.forKnex.createClient());
return Promise.resolve(client);
@ -92,13 +94,37 @@ describe('Ghost Passport', function () {
should.exist(response.passport);
passport.use.callCount.should.eql(2);
models.Client.findOne.called.should.eql(false);
models.Client.findOne.called.should.eql(true);
models.Client.destroy.called.should.eql(false);
models.Client.add.called.should.eql(false);
FakeGhostOAuth2Strategy.prototype.setClient.called.should.eql(false);
FakeGhostOAuth2Strategy.prototype.registerClient.called.should.eql(false);
FakeGhostOAuth2Strategy.prototype.updateClient.called.should.eql(false);
});
});
it('initialise passport with passport auth type [auth client exists]', function () {
return models.Client.add({slug: 'ghost-auth'})
.then(function () {
models.Client.add.called.should.eql(true);
models.Client.add.reset();
return GhostPassport.init({
authType: 'passport'
});
})
.then(function (response) {
should.exist(response.passport);
passport.use.callCount.should.eql(2);
models.Client.findOne.called.should.eql(true);
models.Client.destroy.called.should.eql(true);
models.Client.add.called.should.eql(false);
FakeGhostOAuth2Strategy.prototype.setClient.called.should.eql(false);
FakeGhostOAuth2Strategy.prototype.registerClient.called.should.eql(false);
FakeGhostOAuth2Strategy.prototype.updateClient.called.should.eql(false);
});
});
});
describe('auth_type: ghost', function () {

View File

@ -0,0 +1,78 @@
var should = require('should'),
sinon = require('sinon'),
Promise = require('bluebird'),
auth = require('../../../server/auth'),
models = require('../../../server/models'),
sandbox = sinon.sandbox.create();
describe('UNIT: auth validation', function () {
before(function () {
models.init();
});
beforeEach(function () {
sandbox.restore();
});
describe('ghost is enabled', function () {
it('[success]', function () {
sandbox.stub(models.User, 'isSetup').returns(Promise.resolve(false));
return auth.validation.switch({
authType: 'ghost'
});
});
it('[success]', function () {
sandbox.stub(models.User, 'isSetup').returns(Promise.resolve(true));
sandbox.stub(models.Client, 'findOne').returns(Promise.resolve(true));
return auth.validation.switch({
authType: 'ghost'
});
});
it('[failure]', function () {
sandbox.stub(models.User, 'isSetup').returns(Promise.resolve(true));
sandbox.stub(models.Client, 'findOne').returns(Promise.resolve(true));
return auth.validation.switch({
authType: 'password'
}).catch(function (err) {
should.exist(err);
err.code.should.eql('AUTH_SWITCH');
});
});
});
describe('password is enabled', function () {
it('[success]', function () {
sandbox.stub(models.User, 'isSetup').returns(Promise.resolve(false));
return auth.validation.switch({
authType: 'password'
});
});
it('[success]', function () {
sandbox.stub(models.User, 'isSetup').returns(Promise.resolve(true));
sandbox.stub(models.Client, 'findOne').returns(Promise.resolve(false));
return auth.validation.switch({
authType: 'password'
});
});
it('[failure]', function () {
sandbox.stub(models.User, 'isSetup').returns(Promise.resolve(true));
sandbox.stub(models.Client, 'findOne').returns(Promise.resolve(true));
return auth.validation.switch({
authType: 'ghost'
}).catch(function (err) {
should.exist(err);
err.code.should.eql('AUTH_SWITCH');
});
});
});
});