Merge pull request #2682 from ErisDS/issue-2593

Move user API to primary document format
This commit is contained in:
Hannah Wolfe 2014-05-02 21:14:16 +01:00
commit 2c0ba46383
8 changed files with 265 additions and 146 deletions

View File

@ -1,9 +1,28 @@
/*global Ghost */
/*global Ghost,Backbone */
(function () {
'use strict';
Ghost.Models.User = Ghost.ProgressModel.extend({
url: Ghost.paths.apiRoot + '/users/me/'
url: Ghost.paths.apiRoot + '/users/me/',
parse: function (resp) {
// unwrap user from {users: [{...}]}
if (resp.users) {
resp = resp.users[0];
}
return resp;
},
sync: function (method, model, options) {
// wrap user in {users: [{...}]}
if (method === 'create' || method === 'update') {
options.data = JSON.stringify({users: [this.attributes]});
options.contentType = 'application/json';
}
return Backbone.Model.prototype.sync.apply(this, arguments);
}
});
// Ghost.Collections.Users = Backbone.Collection.extend({

View File

@ -4,9 +4,16 @@ var when = require('when'),
settings = require('./settings'),
canThis = require('../permissions').canThis,
ONE_DAY = 86400000,
filteredAttributes = ['password', 'created_by', 'updated_by', 'last_login'],
filteredAttributes = ['password'],
users;
function checkUserData(userData) {
if (_.isEmpty(userData) || _.isEmpty(userData.users) || _.isEmpty(userData.users[0])) {
return when.reject({code: 400, message: 'No root key (\'users\') provided.'});
}
return when.resolve(userData);
}
// ## Users
users = {
@ -16,8 +23,8 @@ users = {
// **returns:** a promise for a collection of users in a json object
return canThis(this.user).browse.user().then(function () {
return dataProvider.User.browse(options).then(function (result) {
var i = 0,
omitted = {};
var omitted = {},
i;
if (result) {
omitted = result.toJSON();
@ -27,7 +34,7 @@ users = {
omitted[i] = _.omit(omitted[i], filteredAttributes);
}
return omitted;
return { users: omitted };
});
}, function () {
return when.reject({code: 403, message: 'You do not have permission to browse users.'});
@ -45,7 +52,7 @@ users = {
return dataProvider.User.read(args).then(function (result) {
if (result) {
var omitted = _.omit(result.toJSON(), filteredAttributes);
return omitted;
return { users: [omitted] };
}
return when.reject({code: 404, message: 'User not found'});
@ -57,17 +64,18 @@ users = {
edit: function edit(userData) {
// **returns:** a promise for the resulting user in a json object
var self = this;
userData.id = this.user;
return canThis(this.user).edit.user(userData.id).then(function () {
return dataProvider.User.edit(userData, {user: self.user}).then(function (result) {
return canThis(this.user).edit.user(userData.users[0].id).then(function () {
return checkUserData(userData).then(function (checkedUserData) {
return dataProvider.User.edit(checkedUserData.users[0], {user: self.user});
}).then(function (result) {
if (result) {
var omitted = _.omit(result.toJSON(), filteredAttributes);
return omitted;
return { users: [omitted]};
}
return when.reject({code: 404, message: 'User not found'});
});
}, function () {
return when.reject({code: 403, message: 'You do not have permission to edit this users.'});
return when.reject({code: 403, message: 'You do not have permission to edit this user.'});
});
},
@ -77,12 +85,19 @@ users = {
// **returns:** a promise for the resulting user in a json object
var self = this;
return canThis(this.user).add.user().then(function () {
// if the user is created by users.register(), use id: 1
// as the creator for now
if (self.user === 'internal') {
self.user = 1;
}
return dataProvider.User.add(userData, {user: self.user});
return checkUserData(userData).then(function (checkedUserData) {
// if the user is created by users.register(), use id: 1
// as the creator for now
if (self.user === 'internal') {
self.user = 1;
}
return dataProvider.User.add(checkedUserData.users[0], {user: self.user});
}).then(function (result) {
if (result) {
var omitted = _.omit(result.toJSON(), filteredAttributes);
return { users: [omitted]};
}
});
}, function () {
return when.reject({code: 403, message: 'You do not have permission to add a users.'});
});

View File

@ -254,13 +254,14 @@ adminControllers = {
'doSignup': function (req, res) {
var name = req.body.name,
email = req.body.email,
password = req.body.password;
password = req.body.password,
users = [{
name: name,
email: email,
password: password
}];
api.users.register({
name: name,
email: email,
password: password
}).then(function (user) {
api.users.register({users: users}).then(function (user) {
api.settings.edit.call({user: 1}, 'email', email).then(function () {
var message = {
to: email,

View File

@ -42,7 +42,7 @@ function ghostLocals(req, res, next) {
api.users.read.call({user: req.session.user}, {id: req.session.user}),
api.notifications.browse()
]).then(function (values) {
var currentUser = values[0],
var currentUser = values[0].users[0],
notifications = values[1];
_.extend(res.locals, {
@ -239,7 +239,7 @@ function robots() {
if (err) {
return next(err);
}
content = {
headers: {
'Content-Type': 'text/plain',

View File

@ -116,9 +116,8 @@ User = ghostBookshelf.Model.extend({
return userData.roles().attach(1);
}).then(function (addedUserRole) {
/*jshint unused:false*/
// Return the added user as expected
return when.resolve(userData);
// find and return the added user
return self.findOne({id: userData.id}, options);
});
/**
@ -148,7 +147,7 @@ User = ghostBookshelf.Model.extend({
// If we passed in an id instead of a model, get the model
// then check the permissions
if (_.isNumber(userModelOrId) || _.isString(userModelOrId)) {
return this.read({id: userModelOrId, status: 'all'}).then(function (foundUserModel) {
return this.read({id: userModelOrId}).then(function (foundUserModel) {
return self.permissable(foundUserModel, context);
}, errors.logAndThrowError);
}

View File

@ -2,14 +2,12 @@
var supertest = require('supertest'),
express = require('express'),
should = require('should'),
_ = require('lodash'),
testUtils = require('../../../utils'),
ghost = require('../../../../../core'),
httpServer,
request,
agent;
request;
describe('User API', function () {
@ -92,9 +90,11 @@ describe('User API', function () {
should.not.exist(res.headers['x-cache-invalidate']);
res.should.be.json;
var jsonResponse = res.body;
jsonResponse[0].should.exist;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
testUtils.API.checkResponse(jsonResponse[0], 'user');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user');
done();
});
});
@ -110,9 +110,11 @@ describe('User API', function () {
should.not.exist(res.headers['x-cache-invalidate']);
res.should.be.json;
var jsonResponse = res.body;
jsonResponse.should.exist;
jsonResponse.users.should.exist;
testUtils.API.checkResponse(jsonResponse, 'users');
testUtils.API.checkResponse(jsonResponse, 'user');
jsonResponse.users.should.have.length(1);
testUtils.API.checkResponse(jsonResponse.users[0], 'user');
done();
});
});
@ -144,8 +146,8 @@ describe('User API', function () {
var jsonResponse = res.body,
changedValue = 'joe-bloggs.ghost.org';
jsonResponse.should.exist;
jsonResponse.website = changedValue;
jsonResponse.users[0].should.exist;
jsonResponse.users[0].website = changedValue;
request.put(testUtils.API.getApiQuery('users/me/'))
.set('X-CSRF-Token', csrfToken)
@ -159,10 +161,10 @@ describe('User API', function () {
var putBody = res.body;
res.headers['x-cache-invalidate'].should.eql('/*');
res.should.be.json;
putBody.should.exist;
putBody.website.should.eql(changedValue);
putBody.users[0].should.exist;
putBody.users[0].website.should.eql(changedValue);
testUtils.API.checkResponse(putBody, 'user');
testUtils.API.checkResponse(putBody.users[0], 'user');
done();
});
});
@ -177,8 +179,8 @@ describe('User API', function () {
var jsonResponse = res.body,
changedValue = 'joe-bloggs.ghost.org';
jsonResponse.should.exist;
jsonResponse.website = changedValue;
jsonResponse.users[0].should.exist;
jsonResponse.users[0].website = changedValue;
request.put(testUtils.API.getApiQuery('users/me/'))
.set('X-CSRF-Token', 'invalid-token')

View File

@ -3,8 +3,6 @@ var testUtils = require('../../utils'),
should = require('should'),
// Stuff we are testing
permissions = require('../../../server/permissions'),
DataGenerator = require('../../utils/fixtures/data-generator'),
UsersAPI = require('../../../server/api/users');
describe('Users API', function () {
@ -12,98 +10,182 @@ describe('Users API', function () {
before(function (done) {
testUtils.clearData().then(function () {
done();
}, done);
});
beforeEach(function (done) {
testUtils.initData().then(function () {
return testUtils.insertDefaultFixtures();
}).then(function () {
return testUtils.insertEditorUser();
}).then(function () {
return testUtils.insertAuthorUser();
}).then(function () {
done();
}, done);
}).catch(done);
});
afterEach(function (done) {
testUtils.clearData().then(function () {
done();
}, done);
}).catch(done);
});
it('browse', function (done) {
permissions.init().then(function () {
return UsersAPI.browse.call({user: 1});
}).then(function (results) {
should.exist(results);
results.length.should.be.above(0);
testUtils.API.checkResponse(results[0], 'user');
}, function (error) {
done(new Error(JSON.stringify(error)));
}).then(function () {
return UsersAPI.browse.call({user: 2});
}).then(function (results) {
should.exist(results);
results.length.should.be.above(0);
testUtils.API.checkResponse(results[0], 'user');
}, function (error) {
done(new Error(JSON.stringify(error)));
}).then(function () {
return UsersAPI.browse.call({user: 3});
}).then(function (results) {
should.exist(results);
results.length.should.be.above(0);
testUtils.API.checkResponse(results[0], 'user');
done();
}, function (error) {
done(new Error(JSON.stringify(error)));
})
});
it('browse denied', function (done) {
permissions.init().then(function () {
return UsersAPI.browse();
}).then(function (results) {
done(new Error("Browse user is not denied without authentication."));
}, function () {
done();
describe('No User', function () {
beforeEach(function (done) {
testUtils.initData().then(function () {
done();
}).catch(done);
});
it('can add with internal user', function (done) {
UsersAPI.register({ users: [{
'name': 'Hello World',
'email': 'hello@world.com',
'password': 'password'
}]}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
should.exist(results.users);
results.users.should.have.length(1);
testUtils.API.checkResponse(results.users[0], 'user');
results.users[0].name.should.equal('Hello World');
done();
}).catch(done);
});
});
it('read', function (done) {
permissions.init().then(function () {
return UsersAPI.read.call({user: 1}, {id: 1});
}).then(function (result) {
should.exist(result);
result.id.should.eql(1);
testUtils.API.checkResponse(result, 'user');
}, function (error) {
done(new Error(JSON.stringify(error)));
}).then(function () {
return UsersAPI.read.call({user: 2}, {id: 1});
}).then(function (result) {
should.exist(result);
result.id.should.eql(1);
testUtils.API.checkResponse(result, 'user');
}, function (error) {
done(new Error(JSON.stringify(error)));
}).then(function () {
return UsersAPI.read.call({user: 3}, {id: 1});
}).then(function (result) {
should.exist(result);
result.id.should.eql(1);
testUtils.API.checkResponse(result, 'user');
}, function (error) {
done(new Error(JSON.stringify(error)));
}).then(function () {
return UsersAPI.read({id: 1});
}).then(function (result) {
should.exist(result);
result.id.should.eql(1);
testUtils.API.checkResponse(result, 'user');
done();
}, function (error) {
done(new Error(JSON.stringify(error)));
describe('With Users', function () {
beforeEach(function (done) {
testUtils.initData().then(function () {
return testUtils.insertDefaultFixtures();
}).then(function () {
return testUtils.insertEditorUser();
}).then(function () {
return testUtils.insertAuthorUser();
}).then(function () {
done();
}).catch(done);
});
it('admin can browse', function (done) {
UsersAPI.browse.call({user: 1}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
should.exist(results.users);
results.users.should.have.length(3);
testUtils.API.checkResponse(results.users[0], 'user');
testUtils.API.checkResponse(results.users[1], 'user');
testUtils.API.checkResponse(results.users[2], 'user');
done();
}).catch(done);
});
it('editor can browse', function (done) {
UsersAPI.browse.call({user: 2}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
should.exist(results.users);
results.users.should.have.length(3);
testUtils.API.checkResponse(results.users[0], 'user');
testUtils.API.checkResponse(results.users[1], 'user');
testUtils.API.checkResponse(results.users[2], 'user');
done();
}).catch(done);
});
it('author can browse', function (done) {
UsersAPI.browse.call({user: 3}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
should.exist(results.users);
results.users.should.have.length(3);
testUtils.API.checkResponse(results.users[0], 'user');
testUtils.API.checkResponse(results.users[1], 'user');
testUtils.API.checkResponse(results.users[2], 'user');
done();
}).catch(done);
});
it('no-auth user cannot browse', function (done) {
UsersAPI.browse().then(function () {
done(new Error('Browse user is not denied without authentication.'));
}, function () {
done();
}).catch(done);
});
it('admin can read', function (done) {
UsersAPI.read.call({user: 1}, {id: 1}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
results.users[0].id.should.eql(1);
testUtils.API.checkResponse(results.users[0], 'user');
done();
}).catch(done);
});
it('editor can read', function (done) {
UsersAPI.read.call({user: 2}, {id: 1}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
results.users[0].id.should.eql(1);
testUtils.API.checkResponse(results.users[0], 'user');
done();
}).catch(done);
});
it('author can read', function (done) {
UsersAPI.read.call({user: 3}, {id: 1}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
results.users[0].id.should.eql(1);
testUtils.API.checkResponse(results.users[0], 'user');
done();
}).catch(done);
});
it('no-auth can read', function (done) {
UsersAPI.read({id: 1}).then(function (results) {
should.exist(results);
testUtils.API.checkResponse(results, 'users');
results.users[0].id.should.eql(1);
testUtils.API.checkResponse(results.users[0], 'user');
done();
}).catch(done);
});
it('admin can edit', function (done) {
UsersAPI.edit.call({user: 1}, {users: [{id: 1, name: 'Joe Blogger'}]}).then(function (response) {
should.exist(response);
testUtils.API.checkResponse(response, 'users');
response.users.should.have.length(1);
testUtils.API.checkResponse(response.users[0], 'user');
response.users[0].name.should.equal('Joe Blogger');
done();
}).catch(done);
});
it('editor can edit', function (done) {
UsersAPI.edit.call({user: 2}, {users: [{id: 1, name: 'Joe Blogger'}]}).then(function (response) {
should.exist(response);
testUtils.API.checkResponse(response, 'users');
response.users.should.have.length(1);
testUtils.API.checkResponse(response.users[0], 'user');
response.users[0].name.should.eql('Joe Blogger');
done();
}).catch(done);
});
it('author can edit only self', function (done) {
// Test author cannot edit admin user
UsersAPI.edit.call({user: 3}, {users: [{id: 1, name: 'Joe Blogger'}]}).then(function () {
done(new Error('Author should not be able to edit account which is not their own'));
}).catch(function (error) {
error.code.should.eql(403);
}).finally(function () {
// Next test that author CAN edit self
return UsersAPI.edit.call({user: 3}, {users: [{id: 3, name: 'Timothy Bogendath'}]})
.then(function (response) {
should.exist(response);
testUtils.API.checkResponse(response, 'users');
response.users.should.have.length(1);
testUtils.API.checkResponse(response.users[0], 'user');
response.users[0].name.should.eql('Timothy Bogendath');
done();
}).catch(done);
});
});
});
});

View File

@ -1,47 +1,44 @@
var _ = require('lodash'),
url = require('url'),
var url = require('url'),
ApiRouteBase = '/ghost/api/v0.1/',
host = 'localhost',
port = '2369';
schema = "http://",
port = '2369',
schema = 'http://',
expectedProperties = {
posts: ['posts', 'meta'],
users: ['users'],
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
post: ['id', 'uuid', 'title', 'slug', 'markdown', 'html', 'meta_title', 'meta_description',
'featured', 'image', 'status', 'language', 'created_at', 'created_by', 'updated_at',
'updated_by', 'published_at', 'published_by', 'page', 'author', 'tags', 'fields'],
settings: ['settings'],
setting: ['id','uuid','key','value','type','created_at','created_by','updated_at','updated_by'],
setting: ['id', 'uuid', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'updated_by'],
tag: ['id', 'uuid', 'name', 'slug', 'description', 'parent',
'meta_title', 'meta_description', 'created_at', 'created_by', 'updated_at', 'updated_by'],
user: ['id', 'uuid', 'name', 'slug', 'email', 'image', 'cover', 'bio', 'website',
'location', 'accessibility', 'status', 'language', 'meta_title', 'meta_description',
'created_at', 'updated_at'],
'location', 'accessibility', 'status', 'language', 'meta_title', 'meta_description', 'last_login',
'created_at', 'created_by', 'updated_at', 'updated_by'],
notification: ['type', 'message', 'status', 'id']
};
function getApiQuery (route) {
function getApiQuery(route) {
return url.resolve(ApiRouteBase, route);
}
function getApiURL (route) {
function getApiURL(route) {
var baseURL = url.resolve(schema + host + ':' + port, ApiRouteBase);
return url.resolve(baseURL, route);
}
function getSigninURL () {
function getSigninURL() {
return url.resolve(schema + host + ':' + port, 'ghost/signin/');
}
function getAdminURL () {
function getAdminURL() {
return url.resolve(schema + host + ':' + port, 'ghost/');
}
// make sure the API only returns expected properties only
function checkResponse (jsonResponse, objectType) {
checkResponseValue(jsonResponse, expectedProperties[objectType]);
}
function checkResponseValue (jsonResponse, properties) {
function checkResponseValue(jsonResponse, properties) {
Object.keys(jsonResponse).length.should.eql(properties.length);
for(var i=0; i<properties.length; i = i + 1) {
for (var i = 0; i < properties.length; i = i + 1) {
// For some reason, settings response objects do not have the 'hasOwnProperty' method
if (Object.prototype.hasOwnProperty.call(jsonResponse, properties[i])) {
continue;
@ -50,11 +47,15 @@ function checkResponseValue (jsonResponse, properties) {
}
}
function checkResponse(jsonResponse, objectType) {
checkResponseValue(jsonResponse, expectedProperties[objectType]);
}
module.exports = {
getApiURL: getApiURL,
getApiQuery: getApiQuery,
getSigninURL: getSigninURL,
getAdminURL: getAdminURL,
checkResponse: checkResponse,
checkResponseValue: checkResponseValue,
checkResponseValue: checkResponseValue
};