mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-17 23:44:39 -05:00
Merge pull request #3457 from sebgie/issue#3426
Transfer ownership end point
This commit is contained in:
commit
5d3788ffe6
10 changed files with 119 additions and 83 deletions
|
@ -2,27 +2,24 @@ var TransferOwnerController = Ember.Controller.extend({
|
||||||
actions: {
|
actions: {
|
||||||
confirmAccept: function () {
|
confirmAccept: function () {
|
||||||
var user = this.get('model'),
|
var user = this.get('model'),
|
||||||
|
url = this.get('ghostPaths.url').api('users', 'owner'),
|
||||||
self = this;
|
self = this;
|
||||||
|
|
||||||
self.get('popover').closePopovers();
|
self.get('popover').closePopovers();
|
||||||
|
|
||||||
// Get owner role
|
ic.ajax.request(url, {
|
||||||
this.store.find('role').then(function (result) {
|
type: 'PUT',
|
||||||
return result.findBy('name', 'Owner');
|
data: {
|
||||||
}).then(function (ownerRole) {
|
owner: [{
|
||||||
// remove roles and assign owner role
|
'id': user.get('id')
|
||||||
user.get('roles').clear();
|
}]
|
||||||
user.get('roles').pushObject(ownerRole);
|
}
|
||||||
|
|
||||||
return user.saveOnly('roles');
|
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
self.notifications.closePassive();
|
self.notifications.closePassive();
|
||||||
self.notifications.showSuccess('Ownership successfully transferred to ' + user.get('name'));
|
self.notifications.showSuccess('Ownership successfully transferred to ' + user.get('name'));
|
||||||
}).catch(function (errors) {
|
}).catch(function (error) {
|
||||||
self.notifications.closePassive();
|
self.notifications.closePassive();
|
||||||
|
self.notifications.showAPIError(error);
|
||||||
errors = Ember.isArray(errors) ? errors : Ember.A([errors]);
|
|
||||||
self.notifications.showErrors(errors);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,9 @@ roles = {
|
||||||
// TODO: replace with better filter when bluebird lands
|
// TODO: replace with better filter when bluebird lands
|
||||||
_.each(foundRoles.toJSON(), function (role) {
|
_.each(foundRoles.toJSON(), function (role) {
|
||||||
permissionMap.push(canThis(options.context).assign.role(role).then(function () {
|
permissionMap.push(canThis(options.context).assign.role(role).then(function () {
|
||||||
|
if (role.name === 'Owner') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return role;
|
return role;
|
||||||
}, function () {
|
}, function () {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -142,6 +142,11 @@ users = {
|
||||||
}
|
}
|
||||||
|
|
||||||
newUser = checkedUserData.users[0];
|
newUser = checkedUserData.users[0];
|
||||||
|
|
||||||
|
if (_.isEmpty(newUser.roles)) {
|
||||||
|
return when.reject(new errors.BadRequestError('No role provided.'));
|
||||||
|
}
|
||||||
|
|
||||||
roleId = parseInt(newUser.roles[0].id || newUser.roles[0], 10);
|
roleId = parseInt(newUser.roles[0].id || newUser.roles[0], 10);
|
||||||
|
|
||||||
// Make sure user is allowed to add a user with this role
|
// Make sure user is allowed to add a user with this role
|
||||||
|
@ -281,8 +286,26 @@ users = {
|
||||||
return when.reject(new errors.ValidationError(error.message));
|
return when.reject(new errors.ValidationError(error.message));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
transferOwnership: function transferOwnership(object, options) {
|
||||||
|
return dataProvider.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
|
||||||
|
return canThis(options.context).assign.role(ownerRole);
|
||||||
|
}).then(function () {
|
||||||
|
return utils.checkObject(object, 'owner').then(function (checkedOwnerTransfer) {
|
||||||
|
return dataProvider.User.transferOwnership(checkedOwnerTransfer.owner[0], options).then(function () {
|
||||||
|
return when.resolve({owner: [{message: 'Ownership transferred successfully.'}]});
|
||||||
|
}).catch(function (error) {
|
||||||
|
return when.reject(new errors.ValidationError(error.message));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}).catch(function (error) {
|
||||||
|
return errors.handleAPIError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = users;
|
module.exports = users;
|
||||||
|
|
|
@ -58,7 +58,7 @@ createOwner = function () {
|
||||||
var user = fixtures.users[0];
|
var user = fixtures.users[0];
|
||||||
|
|
||||||
return models.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
|
return models.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
|
||||||
user.role = ownerRole.id;
|
user.roles = [ownerRole.id];
|
||||||
user.password = utils.uid(50);
|
user.password = utils.uid(50);
|
||||||
|
|
||||||
logInfo('Creating owner');
|
logInfo('Creating owner');
|
||||||
|
|
|
@ -60,8 +60,9 @@ Role = ghostBookshelf.Model.extend({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (action === 'assign' && loadedPermissions.user) {
|
if (action === 'assign' && loadedPermissions.user) {
|
||||||
if (_.any(loadedPermissions.user.roles, { 'name': 'Owner' }) ||
|
if (_.any(loadedPermissions.user.roles, { 'name': 'Owner' })) {
|
||||||
_.any(loadedPermissions.user.roles, { 'name': 'Administrator' })) {
|
checkAgainst = ['Owner', 'Administrator', 'Editor', 'Author'];
|
||||||
|
} else if (_.any(loadedPermissions.user.roles, { 'name': 'Administrator' })) {
|
||||||
checkAgainst = ['Administrator', 'Editor', 'Author'];
|
checkAgainst = ['Administrator', 'Editor', 'Author'];
|
||||||
} else if (_.any(loadedPermissions.user.roles, { 'name': 'Editor' })) {
|
} else if (_.any(loadedPermissions.user.roles, { 'name': 'Editor' })) {
|
||||||
checkAgainst = ['Author'];
|
checkAgainst = ['Author'];
|
||||||
|
|
|
@ -40,12 +40,6 @@ User = ghostBookshelf.Model.extend({
|
||||||
/*jshint unused:false*/
|
/*jshint unused:false*/
|
||||||
|
|
||||||
var self = this;
|
var self = this;
|
||||||
// disabling sanitization until we can implement a better version
|
|
||||||
// this.set('name', this.sanitize('name'));
|
|
||||||
// this.set('email', this.sanitize('email'));
|
|
||||||
// this.set('location', this.sanitize('location'));
|
|
||||||
// this.set('website', this.sanitize('website'));
|
|
||||||
// this.set('bio', this.sanitize('bio'));
|
|
||||||
|
|
||||||
ghostBookshelf.Model.prototype.saving.apply(this, arguments);
|
ghostBookshelf.Model.prototype.saving.apply(this, arguments);
|
||||||
|
|
||||||
|
@ -184,8 +178,8 @@ User = ghostBookshelf.Model.extend({
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
//TODO: there are multiple statuses that make a user "active" or "invited" - we a way to translate/map them:
|
//TODO: there are multiple statuses that make a user "active" or "invited" - we a way to translate/map them:
|
||||||
//TODO (cont'd from above): * valid "active" statuses: active, warn-1, warn-2, warn-3, warn-4, locked
|
//TODO (cont'd from above): * valid "active" statuses: active, warn-1, warn-2, warn-3, warn-4, locked
|
||||||
//TODO (cont'd from above): * valid "invited" statuses" invited, invited-pending
|
//TODO (cont'd from above): * valid "invited" statuses" invited, invited-pending
|
||||||
|
|
||||||
// Filter on the status. A status of 'all' translates to no filter since we want all statuses
|
// Filter on the status. A status of 'all' translates to no filter since we want all statuses
|
||||||
if (options.status && options.status !== 'all') {
|
if (options.status && options.status !== 'all') {
|
||||||
|
@ -361,7 +355,9 @@ User = ghostBookshelf.Model.extend({
|
||||||
return Role.findOne({id: roleId});
|
return Role.findOne({id: roleId});
|
||||||
}).then(function (roleToAssign) {
|
}).then(function (roleToAssign) {
|
||||||
if (roleToAssign && roleToAssign.get('name') === 'Owner') {
|
if (roleToAssign && roleToAssign.get('name') === 'Owner') {
|
||||||
return self.transferOwnership(user, roleToAssign, options.context);
|
return when.reject(
|
||||||
|
new errors.ValidationError('This method does not support assigning the owner role')
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// assign all other roles
|
// assign all other roles
|
||||||
return user.roles().updatePivot({role_id: roleId});
|
return user.roles().updatePivot({role_id: roleId});
|
||||||
|
@ -386,24 +382,23 @@ User = ghostBookshelf.Model.extend({
|
||||||
*/
|
*/
|
||||||
add: function (data, options) {
|
add: function (data, options) {
|
||||||
var self = this,
|
var self = this,
|
||||||
// Clone the _user so we don't expose the hashed password unnecessarily
|
|
||||||
userData = this.filterData(data),
|
userData = this.filterData(data),
|
||||||
// Get the role we're going to assign to this user, or the author role if there isn't one
|
roles;
|
||||||
// TODO: don't reference Author role by ID!
|
|
||||||
roles = data.roles || [1];
|
|
||||||
|
|
||||||
// remove roles from the object
|
|
||||||
delete data.roles;
|
|
||||||
|
|
||||||
// check for too many roles
|
|
||||||
if (roles.length > 1) {
|
|
||||||
return when.reject(new errors.ValidationError('Only one role per user is supported at the moment.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
options = this.filterOptions(options, 'add');
|
options = this.filterOptions(options, 'add');
|
||||||
options.withRelated = _.union([ 'roles' ], options.include);
|
options.withRelated = _.union([ 'roles' ], options.include);
|
||||||
|
return Role.findOne({name: 'Author'}).then(function (authorRole) {
|
||||||
|
// Get the role we're going to assign to this user, or the author role if there isn't one
|
||||||
|
roles = data.roles || authorRole.id;
|
||||||
|
// check for too many roles
|
||||||
|
if (roles.length > 1) {
|
||||||
|
return when.reject(new errors.ValidationError('Only one role per user is supported at the moment.'));
|
||||||
|
}
|
||||||
|
// remove roles from the object
|
||||||
|
delete data.roles;
|
||||||
|
|
||||||
return validatePasswordLength(userData.password).then(function () {
|
return validatePasswordLength(userData.password);
|
||||||
|
}).then(function () {
|
||||||
return self.forge().fetch();
|
return self.forge().fetch();
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
// Generate a new password hash
|
// Generate a new password hash
|
||||||
|
@ -720,23 +715,40 @@ User = ghostBookshelf.Model.extend({
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
transferOwnership: function (user, ownerRole, context) {
|
transferOwnership: function (object, options) {
|
||||||
var adminRole;
|
var adminRole,
|
||||||
|
ownerRole,
|
||||||
|
contextUser,
|
||||||
|
assignUser;
|
||||||
|
|
||||||
// Get admin role
|
// Get admin role
|
||||||
return Role.findOne({name: 'Administrator'}).then(function (result) {
|
return Role.findOne({name: 'Administrator'}).then(function (result) {
|
||||||
adminRole = result;
|
adminRole = result;
|
||||||
return User.findOne({id: context.user});
|
return Role.findOne({name: 'Owner'});
|
||||||
}).then(function (contextUser) {
|
}).then(function (result) {
|
||||||
|
ownerRole = result;
|
||||||
|
return User.findOne({id: options.context.user});
|
||||||
|
}).then(function (ctxUser) {
|
||||||
// check if user has the owner role
|
// check if user has the owner role
|
||||||
var currentRoles = contextUser.toJSON().roles;
|
var currentRoles = ctxUser.toJSON().roles;
|
||||||
if (!_.contains(currentRoles, ownerRole.id)) {
|
if (!_.contains(currentRoles, ownerRole.id)) {
|
||||||
return when.reject(new errors.NoPermissionError('Only owners are able to transfer the owner role.'));
|
return when.reject(new errors.NoPermissionError('Only owners are able to transfer the owner role.'));
|
||||||
}
|
}
|
||||||
|
contextUser = ctxUser;
|
||||||
|
return User.findOne({id: object.id});
|
||||||
|
}).then(function (user) {
|
||||||
|
|
||||||
|
var currentRoles = user.toJSON().roles;
|
||||||
|
if (!_.contains(currentRoles, adminRole.id)) {
|
||||||
|
return when.reject(new errors.ValidationError('Only administrators can be assigned the owner role.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
assignUser = user;
|
||||||
// convert owner to admin
|
// convert owner to admin
|
||||||
return contextUser.roles().updatePivot({role_id: adminRole.id});
|
return contextUser.roles().updatePivot({role_id: adminRole.id});
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
// assign owner role to a new user
|
// assign owner role to a new user
|
||||||
return user.roles().updatePivot({role_id: ownerRole.id});
|
return assignUser.roles().updatePivot({role_id: ownerRole.id});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,7 @@ apiRoutes = function (middleware) {
|
||||||
router.get('/users/slug/:slug', api.http(api.users.read));
|
router.get('/users/slug/:slug', api.http(api.users.read));
|
||||||
router.get('/users/email/:email', api.http(api.users.read));
|
router.get('/users/email/:email', api.http(api.users.read));
|
||||||
router.put('/users/password', api.http(api.users.changePassword));
|
router.put('/users/password', api.http(api.users.changePassword));
|
||||||
|
router.put('/users/owner', api.http(api.users.transferOwnership));
|
||||||
router.put('/users/:id', api.http(api.users.edit));
|
router.put('/users/:id', api.http(api.users.edit));
|
||||||
router.post('/users', api.http(api.users.add));
|
router.post('/users', api.http(api.users.add));
|
||||||
router.del('/users/:id', api.http(api.users.destroy));
|
router.del('/users/:id', api.http(api.users.destroy));
|
||||||
|
|
|
@ -856,47 +856,41 @@ describe('Users API', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Transfer ownership', function () {
|
describe('Transfer ownership', function () {
|
||||||
// Temporarily commenting this test out until #3426 is fixed
|
it('Owner can transfer ownership', function (done) {
|
||||||
// it('Owner can transfer ownership', function (done) {
|
// transfer ownership to admin user id:2
|
||||||
// // transfer ownership to admin user id:2
|
UserAPI.transferOwnership(
|
||||||
// UserAPI.edit(
|
{owner: [
|
||||||
// {users: [
|
{id: userIdFor.admin}
|
||||||
// {name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
]}, context.owner
|
||||||
// ]}, _.extend({}, context.owner, {id: userIdFor.admin})
|
).then(function (response) {
|
||||||
// ).then(function (response) {
|
should.exist(response);
|
||||||
// should.exist(response);
|
should.exist(response.owner);
|
||||||
// should.not.exist(response.meta);
|
response.owner.should.have.length(1);
|
||||||
// should.exist(response.users);
|
response.owner[0].message.should.eql('Ownership transferred successfully.');
|
||||||
// response.users.should.have.length(1);
|
done();
|
||||||
// testUtils.API.checkResponse(response.users[0], 'user', ['roles']);
|
}).catch(done);
|
||||||
// response.users[0].name.should.equal('Joe Blogger');
|
});
|
||||||
// response.users[0].id.should.equal(2);
|
|
||||||
// response.users[0].roles[0].should.equal(4);
|
|
||||||
// response.users[0].updated_at.should.be.a.Date;
|
|
||||||
// done();
|
|
||||||
// }).catch(done);
|
|
||||||
// });
|
|
||||||
|
|
||||||
it('Owner CANNOT downgrade own role', function (done) {
|
it('Owner CANNOT downgrade own role', function (done) {
|
||||||
// Cannot change own role to admin
|
// Cannot change own role to admin
|
||||||
UserAPI.edit(
|
UserAPI.transferOwnership(
|
||||||
{users: [
|
{owner: [
|
||||||
{name: 'Joe Blogger', roles: [roleIdFor.admin]}
|
{id: userIdFor.owner}
|
||||||
]}, _.extend({}, context.owner, {id: userIdFor.owner})
|
]}, context.owner
|
||||||
).then(function (response) {
|
).then(function (response) {
|
||||||
done(new Error('Owner should not be able to downgrade their role'));
|
done(new Error('Owner should not be able to downgrade their role'));
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
error.type.should.eql('NoPermissionError');
|
error.type.should.eql('ValidationError');
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Admin CANNOT transfer ownership', function (done) {
|
it('Admin CANNOT transfer ownership', function (done) {
|
||||||
// transfer ownership to user id: 2
|
// transfer ownership to user id: 2
|
||||||
UserAPI.edit(
|
UserAPI.transferOwnership(
|
||||||
{users: [
|
{owner: [
|
||||||
{name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
{id: userIdFor.editor}
|
||||||
]}, _.extend({}, context.admin, {id: userIdFor.admin})
|
]}, context.admin
|
||||||
).then(function () {
|
).then(function () {
|
||||||
done(new Error('Admin is not denied transferring ownership.'));
|
done(new Error('Admin is not denied transferring ownership.'));
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
|
@ -907,10 +901,10 @@ describe('Users API', function () {
|
||||||
|
|
||||||
it('Editor CANNOT transfer ownership', function (done) {
|
it('Editor CANNOT transfer ownership', function (done) {
|
||||||
// transfer ownership to user id: 2
|
// transfer ownership to user id: 2
|
||||||
UserAPI.edit(
|
UserAPI.transferOwnership(
|
||||||
{users: [
|
{owner: [
|
||||||
{name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
{id: userIdFor.admin}
|
||||||
]}, _.extend({}, context.editor, {id: userIdFor.admin})
|
]}, context.editor
|
||||||
).then(function () {
|
).then(function () {
|
||||||
done(new Error('Admin is not denied transferring ownership.'));
|
done(new Error('Admin is not denied transferring ownership.'));
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
|
@ -921,10 +915,10 @@ describe('Users API', function () {
|
||||||
|
|
||||||
it('Author CANNOT transfer ownership', function (done) {
|
it('Author CANNOT transfer ownership', function (done) {
|
||||||
// transfer ownership to user id: 2
|
// transfer ownership to user id: 2
|
||||||
UserAPI.edit(
|
UserAPI.transferOwnership(
|
||||||
{users: [
|
{owner: [
|
||||||
{name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
{id: userIdFor.admin}
|
||||||
]}, _.extend({}, context.author, {id: userIdFor.admin})
|
]}, context.author
|
||||||
).then(function () {
|
).then(function () {
|
||||||
done(new Error('Admin is not denied transferring ownership.'));
|
done(new Error('Admin is not denied transferring ownership.'));
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
|
|
|
@ -25,7 +25,7 @@ describe('User Model', function run() {
|
||||||
should.exist(UserModel);
|
should.exist(UserModel);
|
||||||
|
|
||||||
describe('Registration', function runRegistration() {
|
describe('Registration', function runRegistration() {
|
||||||
beforeEach(testUtils.setup());
|
beforeEach(testUtils.setup('roles'));
|
||||||
|
|
||||||
it('can add first', function (done) {
|
it('can add first', function (done) {
|
||||||
var userData = testUtils.DataGenerator.forModel.users[0];
|
var userData = testUtils.DataGenerator.forModel.users[0];
|
||||||
|
|
|
@ -101,6 +101,10 @@ fixtures = {
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
insertRoles: function insertRoles() {
|
||||||
|
var knex = config.database.knex;
|
||||||
|
return knex('roles').insert(DataGenerator.forKnex.roles);
|
||||||
|
},
|
||||||
|
|
||||||
initOwnerUser: function initOwnerUser() {
|
initOwnerUser: function initOwnerUser() {
|
||||||
var user = DataGenerator.Content.users[0],
|
var user = DataGenerator.Content.users[0],
|
||||||
|
@ -258,6 +262,7 @@ toDoList = {
|
||||||
},
|
},
|
||||||
'permission': function insertPermission() { return fixtures.insertOne('permissions', 'createPermission'); },
|
'permission': function insertPermission() { return fixtures.insertOne('permissions', 'createPermission'); },
|
||||||
'role': function insertRole() { return fixtures.insertOne('roles', 'createRole'); },
|
'role': function insertRole() { return fixtures.insertOne('roles', 'createRole'); },
|
||||||
|
'roles': function insertRoles() { return fixtures.insertRoles(); },
|
||||||
'tag': function insertRole() { return fixtures.insertOne('tags', 'createTag'); },
|
'tag': function insertRole() { return fixtures.insertOne('tags', 'createTag'); },
|
||||||
'posts': function insertPosts() { return fixtures.insertPosts(); },
|
'posts': function insertPosts() { return fixtures.insertPosts(); },
|
||||||
'apps': function insertApps() { return fixtures.insertApps(); },
|
'apps': function insertApps() { return fixtures.insertApps(); },
|
||||||
|
|
Loading…
Add table
Reference in a new issue