mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -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: {
|
||||
confirmAccept: function () {
|
||||
var user = this.get('model'),
|
||||
url = this.get('ghostPaths.url').api('users', 'owner'),
|
||||
self = this;
|
||||
|
||||
self.get('popover').closePopovers();
|
||||
|
||||
// Get owner role
|
||||
this.store.find('role').then(function (result) {
|
||||
return result.findBy('name', 'Owner');
|
||||
}).then(function (ownerRole) {
|
||||
// remove roles and assign owner role
|
||||
user.get('roles').clear();
|
||||
user.get('roles').pushObject(ownerRole);
|
||||
|
||||
return user.saveOnly('roles');
|
||||
ic.ajax.request(url, {
|
||||
type: 'PUT',
|
||||
data: {
|
||||
owner: [{
|
||||
'id': user.get('id')
|
||||
}]
|
||||
}
|
||||
}).then(function () {
|
||||
self.notifications.closePassive();
|
||||
self.notifications.showSuccess('Ownership successfully transferred to ' + user.get('name'));
|
||||
}).catch(function (errors) {
|
||||
}).catch(function (error) {
|
||||
self.notifications.closePassive();
|
||||
|
||||
errors = Ember.isArray(errors) ? errors : Ember.A([errors]);
|
||||
self.notifications.showErrors(errors);
|
||||
self.notifications.showAPIError(error);
|
||||
});
|
||||
},
|
||||
|
||||
|
|
|
@ -41,6 +41,9 @@ roles = {
|
|||
// TODO: replace with better filter when bluebird lands
|
||||
_.each(foundRoles.toJSON(), function (role) {
|
||||
permissionMap.push(canThis(options.context).assign.role(role).then(function () {
|
||||
if (role.name === 'Owner') {
|
||||
return null;
|
||||
}
|
||||
return role;
|
||||
}, function () {
|
||||
return null;
|
||||
|
|
|
@ -142,6 +142,11 @@ users = {
|
|||
}
|
||||
|
||||
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);
|
||||
|
||||
// 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));
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
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;
|
||||
|
|
|
@ -58,7 +58,7 @@ createOwner = function () {
|
|||
var user = fixtures.users[0];
|
||||
|
||||
return models.Role.findOne({name: 'Owner'}).then(function (ownerRole) {
|
||||
user.role = ownerRole.id;
|
||||
user.roles = [ownerRole.id];
|
||||
user.password = utils.uid(50);
|
||||
|
||||
logInfo('Creating owner');
|
||||
|
|
|
@ -60,8 +60,9 @@ Role = ghostBookshelf.Model.extend({
|
|||
}
|
||||
|
||||
if (action === 'assign' && loadedPermissions.user) {
|
||||
if (_.any(loadedPermissions.user.roles, { 'name': 'Owner' }) ||
|
||||
_.any(loadedPermissions.user.roles, { 'name': 'Administrator' })) {
|
||||
if (_.any(loadedPermissions.user.roles, { 'name': 'Owner' })) {
|
||||
checkAgainst = ['Owner', 'Administrator', 'Editor', 'Author'];
|
||||
} else if (_.any(loadedPermissions.user.roles, { 'name': 'Administrator' })) {
|
||||
checkAgainst = ['Administrator', 'Editor', 'Author'];
|
||||
} else if (_.any(loadedPermissions.user.roles, { 'name': 'Editor' })) {
|
||||
checkAgainst = ['Author'];
|
||||
|
|
|
@ -40,12 +40,6 @@ User = ghostBookshelf.Model.extend({
|
|||
/*jshint unused:false*/
|
||||
|
||||
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);
|
||||
|
||||
|
@ -184,8 +178,8 @@ User = ghostBookshelf.Model.extend({
|
|||
}, options);
|
||||
|
||||
//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 "invited" statuses" invited, invited-pending
|
||||
//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
|
||||
|
||||
// Filter on the status. A status of 'all' translates to no filter since we want all statuses
|
||||
if (options.status && options.status !== 'all') {
|
||||
|
@ -361,7 +355,9 @@ User = ghostBookshelf.Model.extend({
|
|||
return Role.findOne({id: roleId});
|
||||
}).then(function (roleToAssign) {
|
||||
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 {
|
||||
// assign all other roles
|
||||
return user.roles().updatePivot({role_id: roleId});
|
||||
|
@ -386,24 +382,23 @@ User = ghostBookshelf.Model.extend({
|
|||
*/
|
||||
add: function (data, options) {
|
||||
var self = this,
|
||||
// Clone the _user so we don't expose the hashed password unnecessarily
|
||||
userData = this.filterData(data),
|
||||
// Get the role we're going to assign to this user, or the author role if there isn't one
|
||||
// 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.'));
|
||||
}
|
||||
roles;
|
||||
|
||||
options = this.filterOptions(options, 'add');
|
||||
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();
|
||||
}).then(function () {
|
||||
// Generate a new password hash
|
||||
|
@ -720,23 +715,40 @@ User = ghostBookshelf.Model.extend({
|
|||
});
|
||||
},
|
||||
|
||||
transferOwnership: function (user, ownerRole, context) {
|
||||
var adminRole;
|
||||
transferOwnership: function (object, options) {
|
||||
var adminRole,
|
||||
ownerRole,
|
||||
contextUser,
|
||||
assignUser;
|
||||
|
||||
// Get admin role
|
||||
return Role.findOne({name: 'Administrator'}).then(function (result) {
|
||||
adminRole = result;
|
||||
return User.findOne({id: context.user});
|
||||
}).then(function (contextUser) {
|
||||
return Role.findOne({name: 'Owner'});
|
||||
}).then(function (result) {
|
||||
ownerRole = result;
|
||||
return User.findOne({id: options.context.user});
|
||||
}).then(function (ctxUser) {
|
||||
// check if user has the owner role
|
||||
var currentRoles = contextUser.toJSON().roles;
|
||||
var currentRoles = ctxUser.toJSON().roles;
|
||||
if (!_.contains(currentRoles, ownerRole.id)) {
|
||||
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
|
||||
return contextUser.roles().updatePivot({role_id: adminRole.id});
|
||||
}).then(function () {
|
||||
// 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/email/:email', api.http(api.users.read));
|
||||
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.post('/users', api.http(api.users.add));
|
||||
router.del('/users/:id', api.http(api.users.destroy));
|
||||
|
|
|
@ -856,47 +856,41 @@ describe('Users API', function () {
|
|||
});
|
||||
|
||||
describe('Transfer ownership', function () {
|
||||
// Temporarily commenting this test out until #3426 is fixed
|
||||
// it('Owner can transfer ownership', function (done) {
|
||||
// // transfer ownership to admin user id:2
|
||||
// UserAPI.edit(
|
||||
// {users: [
|
||||
// {name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
||||
// ]}, _.extend({}, context.owner, {id: userIdFor.admin})
|
||||
// ).then(function (response) {
|
||||
// should.exist(response);
|
||||
// should.not.exist(response.meta);
|
||||
// should.exist(response.users);
|
||||
// response.users.should.have.length(1);
|
||||
// testUtils.API.checkResponse(response.users[0], 'user', ['roles']);
|
||||
// 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 can transfer ownership', function (done) {
|
||||
// transfer ownership to admin user id:2
|
||||
UserAPI.transferOwnership(
|
||||
{owner: [
|
||||
{id: userIdFor.admin}
|
||||
]}, context.owner
|
||||
).then(function (response) {
|
||||
should.exist(response);
|
||||
should.exist(response.owner);
|
||||
response.owner.should.have.length(1);
|
||||
response.owner[0].message.should.eql('Ownership transferred successfully.');
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('Owner CANNOT downgrade own role', function (done) {
|
||||
// Cannot change own role to admin
|
||||
UserAPI.edit(
|
||||
{users: [
|
||||
{name: 'Joe Blogger', roles: [roleIdFor.admin]}
|
||||
]}, _.extend({}, context.owner, {id: userIdFor.owner})
|
||||
UserAPI.transferOwnership(
|
||||
{owner: [
|
||||
{id: userIdFor.owner}
|
||||
]}, context.owner
|
||||
).then(function (response) {
|
||||
done(new Error('Owner should not be able to downgrade their role'));
|
||||
}).catch(function (error) {
|
||||
error.type.should.eql('NoPermissionError');
|
||||
error.type.should.eql('ValidationError');
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('Admin CANNOT transfer ownership', function (done) {
|
||||
// transfer ownership to user id: 2
|
||||
UserAPI.edit(
|
||||
{users: [
|
||||
{name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
||||
]}, _.extend({}, context.admin, {id: userIdFor.admin})
|
||||
UserAPI.transferOwnership(
|
||||
{owner: [
|
||||
{id: userIdFor.editor}
|
||||
]}, context.admin
|
||||
).then(function () {
|
||||
done(new Error('Admin is not denied transferring ownership.'));
|
||||
}).catch(function (error) {
|
||||
|
@ -907,10 +901,10 @@ describe('Users API', function () {
|
|||
|
||||
it('Editor CANNOT transfer ownership', function (done) {
|
||||
// transfer ownership to user id: 2
|
||||
UserAPI.edit(
|
||||
{users: [
|
||||
{name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
||||
]}, _.extend({}, context.editor, {id: userIdFor.admin})
|
||||
UserAPI.transferOwnership(
|
||||
{owner: [
|
||||
{id: userIdFor.admin}
|
||||
]}, context.editor
|
||||
).then(function () {
|
||||
done(new Error('Admin is not denied transferring ownership.'));
|
||||
}).catch(function (error) {
|
||||
|
@ -921,10 +915,10 @@ describe('Users API', function () {
|
|||
|
||||
it('Author CANNOT transfer ownership', function (done) {
|
||||
// transfer ownership to user id: 2
|
||||
UserAPI.edit(
|
||||
{users: [
|
||||
{name: 'Joe Blogger', roles: [roleIdFor.owner]}
|
||||
]}, _.extend({}, context.author, {id: userIdFor.admin})
|
||||
UserAPI.transferOwnership(
|
||||
{owner: [
|
||||
{id: userIdFor.admin}
|
||||
]}, context.author
|
||||
).then(function () {
|
||||
done(new Error('Admin is not denied transferring ownership.'));
|
||||
}).catch(function (error) {
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('User Model', function run() {
|
|||
should.exist(UserModel);
|
||||
|
||||
describe('Registration', function runRegistration() {
|
||||
beforeEach(testUtils.setup());
|
||||
beforeEach(testUtils.setup('roles'));
|
||||
|
||||
it('can add first', function (done) {
|
||||
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() {
|
||||
var user = DataGenerator.Content.users[0],
|
||||
|
@ -258,6 +262,7 @@ toDoList = {
|
|||
},
|
||||
'permission': function insertPermission() { return fixtures.insertOne('permissions', 'createPermission'); },
|
||||
'role': function insertRole() { return fixtures.insertOne('roles', 'createRole'); },
|
||||
'roles': function insertRoles() { return fixtures.insertRoles(); },
|
||||
'tag': function insertRole() { return fixtures.insertOne('tags', 'createTag'); },
|
||||
'posts': function insertPosts() { return fixtures.insertPosts(); },
|
||||
'apps': function insertApps() { return fixtures.insertApps(); },
|
||||
|
|
Loading…
Add table
Reference in a new issue