mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-10 23:36:14 -05:00
Agressive stripping of the model attributes
- fixes #517 - prevents this from occuring again in future with other relations - validation function & stripping done for all models - casper test for flow, plus validation & logged out tests
This commit is contained in:
parent
41e36cca7e
commit
c70dfde7e3
11 changed files with 296 additions and 10 deletions
|
@ -7,6 +7,25 @@ var GhostBookshelf = require('./base'),
|
|||
Permission = GhostBookshelf.Model.extend({
|
||||
tableName: 'permissions',
|
||||
|
||||
permittedAttributes: ['id', 'name', 'object_type', 'action_type', 'object_id'],
|
||||
|
||||
initialize: function () {
|
||||
this.on('saving', this.saving, this);
|
||||
this.on('saving', this.validate, this);
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
// TODO: validate object_type, action_type and object_id
|
||||
GhostBookshelf.validator.check(this.get('name'), "Permission name cannot be blank").notEmpty();
|
||||
},
|
||||
|
||||
saving: function () {
|
||||
// Deal with the related data here
|
||||
|
||||
// Remove any properties which don't belong on the post model
|
||||
this.attributes = this.pick(this.permittedAttributes);
|
||||
},
|
||||
|
||||
roles: function () {
|
||||
return this.belongsToMany(Role);
|
||||
},
|
||||
|
|
|
@ -13,6 +13,12 @@ Post = GhostBookshelf.Model.extend({
|
|||
|
||||
tableName: 'posts',
|
||||
|
||||
permittedAttributes: [
|
||||
'id', 'uuid', 'title', 'slug', 'content_raw', 'content', 'meta_title', 'meta_description', 'meta_keywords',
|
||||
'featured', 'image', 'status', 'language', 'author_id', 'created_at', 'created_by', 'updated_at', 'updated_by',
|
||||
'published_at', 'published_by'
|
||||
],
|
||||
|
||||
hasTimestamps: true,
|
||||
|
||||
defaults: function () {
|
||||
|
@ -24,9 +30,9 @@ Post = GhostBookshelf.Model.extend({
|
|||
},
|
||||
|
||||
initialize: function () {
|
||||
this.on('saving', this.validate, this);
|
||||
this.on('creating', this.creating, this);
|
||||
this.on('saving', this.saving, this);
|
||||
this.on('saving', this.validate, this);
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
|
@ -36,6 +42,11 @@ Post = GhostBookshelf.Model.extend({
|
|||
},
|
||||
|
||||
saving: function () {
|
||||
// Deal with the related data here
|
||||
|
||||
// Remove any properties which don't belong on the post model
|
||||
this.attributes = this.pick(this.permittedAttributes);
|
||||
|
||||
this.set('content', converter.makeHtml(this.get('content_raw')));
|
||||
|
||||
if (this.hasChanged('status') && this.get('status') === 'published') {
|
||||
|
|
|
@ -7,6 +7,25 @@ var User = require('./user').User,
|
|||
Role = GhostBookshelf.Model.extend({
|
||||
tableName: 'roles',
|
||||
|
||||
permittedAttributes: ['id', 'name', 'description'],
|
||||
|
||||
initialize: function () {
|
||||
this.on('saving', this.saving, this);
|
||||
this.on('saving', this.validate, this);
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
GhostBookshelf.validator.check(this.get('name'), "Role name cannot be blank").notEmpty();
|
||||
GhostBookshelf.validator.check(this.get('description'), "Role description cannot be blank").notEmpty();
|
||||
},
|
||||
|
||||
saving: function () {
|
||||
// Deal with the related data here
|
||||
|
||||
// Remove any properties which don't belong on the post model
|
||||
this.attributes = this.pick(this.permittedAttributes);
|
||||
},
|
||||
|
||||
users: function () {
|
||||
return this.belongsToMany(User);
|
||||
},
|
||||
|
|
|
@ -8,13 +8,36 @@ var Settings,
|
|||
// Each setting is saved as a separate row in the database,
|
||||
// but the overlying API treats them as a single key:value mapping
|
||||
Settings = GhostBookshelf.Model.extend({
|
||||
|
||||
tableName: 'settings',
|
||||
|
||||
hasTimestamps: true,
|
||||
|
||||
permittedAttributes: ['id', 'uuid', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'update_by'],
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
uuid: uuid.v4(),
|
||||
type: 'general'
|
||||
};
|
||||
},
|
||||
|
||||
initialize: function () {
|
||||
this.on('saving', this.saving, this);
|
||||
this.on('saving', this.validate, this);
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
// TODO: validate value, check type is one of the allowed values etc
|
||||
GhostBookshelf.validator.check(this.get('key'), "Setting key cannot be blank").notEmpty();
|
||||
GhostBookshelf.validator.check(this.get('type'), "Setting type cannot be blank").notEmpty();
|
||||
},
|
||||
|
||||
saving: function () {
|
||||
// Deal with the related data here
|
||||
|
||||
// Remove any properties which don't belong on the post model
|
||||
this.attributes = this.pick(this.permittedAttributes);
|
||||
}
|
||||
}, {
|
||||
read: function (_key) {
|
||||
|
|
|
@ -34,6 +34,11 @@ User = GhostBookshelf.Model.extend({
|
|||
|
||||
hasTimestamps: true,
|
||||
|
||||
permittedAttributes: [
|
||||
'id', 'uuid', 'full_name', 'password', 'email_address', 'profile_picture', 'cover_picture', 'bio', 'url', 'location',
|
||||
'created_at', 'created_by', 'updated_at', 'updated_by'
|
||||
],
|
||||
|
||||
defaults: function () {
|
||||
return {
|
||||
uuid: uuid.v4()
|
||||
|
@ -41,6 +46,7 @@ User = GhostBookshelf.Model.extend({
|
|||
},
|
||||
|
||||
initialize: function () {
|
||||
this.on('saving', this.saving, this);
|
||||
this.on('saving', this.validate, this);
|
||||
},
|
||||
|
||||
|
@ -53,6 +59,13 @@ User = GhostBookshelf.Model.extend({
|
|||
return true;
|
||||
},
|
||||
|
||||
saving: function () {
|
||||
// Deal with the related data here
|
||||
|
||||
// Remove any properties which don't belong on the post model
|
||||
this.attributes = this.pick(this.permittedAttributes);
|
||||
},
|
||||
|
||||
posts: function () {
|
||||
return this.hasMany(Posts, 'created_by');
|
||||
},
|
||||
|
|
|
@ -37,6 +37,7 @@ casper.test.begin("Can login to Ghost", 3, function suite(test) {
|
|||
casper.waitFor(function checkOpaque() {
|
||||
return this.evaluate(function () {
|
||||
var loginBox = document.querySelector('.login-box');
|
||||
|
||||
return window.getComputedStyle(loginBox).getPropertyValue('display') === "block"
|
||||
&& window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1";
|
||||
});
|
||||
|
@ -62,7 +63,7 @@ casper.test.begin("Can't spam it", 2, function suite(test) {
|
|||
|
||||
casper.test.filename = "login_test.png";
|
||||
|
||||
casper.start(url + "ghost/signin/", function testTitle() {
|
||||
casper.start(url + "ghost/login/", function testTitle() {
|
||||
test.assertTitle("", "Ghost admin has no title");
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*globals casper, __utils__, url, testPost */
|
||||
|
||||
casper.test.begin("Ghost editor is correct", 8, function suite(test) {
|
||||
casper.test.begin("Ghost editor is correct", 10, function suite(test) {
|
||||
|
||||
casper.test.filename = "editor_test.png";
|
||||
|
||||
|
@ -18,6 +18,12 @@ casper.test.begin("Ghost editor is correct", 8, function suite(test) {
|
|||
}
|
||||
}
|
||||
|
||||
// test saving with no data
|
||||
casper.thenClick('.button-save').wait(500, function doneWait() {
|
||||
test.assertExists('.notification-error', 'got error notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
});
|
||||
|
||||
casper.then(function createTestPost() {
|
||||
casper.sendKeys('#entry-title', testPost.title);
|
||||
casper.writeContentToCodeMirror(testPost.content);
|
||||
|
@ -30,11 +36,7 @@ casper.test.begin("Ghost editor is correct", 8, function suite(test) {
|
|||
casper.on('resource.received', handleResource);
|
||||
});
|
||||
|
||||
casper.thenClick('.button-save').wait(1000, function doneWait() {
|
||||
this.echo("I've waited for another 1 seconds.");
|
||||
});
|
||||
|
||||
casper.then(function checkPostWasCreated() {
|
||||
casper.thenClick('.button-save').waitForResource(/posts/, function checkPostWasCreated() {
|
||||
var urlRegExp = new RegExp("^" + url + "ghost\/editor\/[0-9]*");
|
||||
test.assertUrlMatch(urlRegExp, 'got an id on our URL');
|
||||
test.assertExists('.notification-success', 'got success notification');
|
||||
|
|
|
@ -113,4 +113,44 @@ casper.test.begin("Settings screen is correct", 19, function suite(test) {
|
|||
casper.removeListener('resource.requested', handleSettingsRequest);
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
|
||||
casper.test.begin("User settings screen validates email", 6, function suite(test) {
|
||||
var email, brokenEmail;
|
||||
|
||||
casper.test.filename = "user_settings_test.png";
|
||||
|
||||
casper.start(url + "ghost/settings/user", function testTitleAndUrl() {
|
||||
test.assertTitle("", "Ghost admin has no title");
|
||||
test.assertEquals(this.getCurrentUrl(), url + "ghost/settings/user", "Ghost doesn't require login this time");
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
casper.then(function setEmailToInvalid() {
|
||||
email = casper.getElementInfo('#user-email').attributes.value;
|
||||
brokenEmail = email.replace('.', '-');
|
||||
|
||||
casper.fillSelectors('.user-details-container', {
|
||||
'#user-email': brokenEmail
|
||||
}, false);
|
||||
});
|
||||
|
||||
casper.thenClick('#user .button-save').waitForResource('/users/', function () {
|
||||
test.assertExists('.notification-error', 'got error notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
});
|
||||
|
||||
casper.then(function resetEmailToValid() {
|
||||
casper.fillSelectors('.user-details-container', {
|
||||
'#user-email': email
|
||||
}, false);
|
||||
});
|
||||
|
||||
casper.thenClick('#user .button-save').waitForResource('/users/', function () {
|
||||
test.assertExists('.notification-success', 'got success notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-success', '[object Object]');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
60
core/test/functional/admin/06_flow_test.js
Normal file
60
core/test/functional/admin/06_flow_test.js
Normal file
|
@ -0,0 +1,60 @@
|
|||
/**
|
||||
* Tests the flow of creating, editing and publishing tests
|
||||
*/
|
||||
|
||||
/*globals casper, __utils__, url, testPost */
|
||||
casper.test.begin("Ghost edit draft flow works correctly", 7, function suite(test) {
|
||||
|
||||
casper.test.filename = "flow_test.png";
|
||||
|
||||
casper.start(url + "ghost/editor", function then() {
|
||||
test.assertEquals(casper.getCurrentUrl(), url + "ghost/editor", "Ghost doesn't require login this time");
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
// First, create a new draft post
|
||||
casper.then(function createTestPost() {
|
||||
casper.sendKeys('#entry-title', 'Test Draft Post');
|
||||
casper.writeContentToCodeMirror('I am a draft');
|
||||
});
|
||||
|
||||
// We must wait after sending keys to CodeMirror
|
||||
casper.wait(1000, function doneWait() {
|
||||
this.echo("I've waited for 1 seconds.");
|
||||
});
|
||||
|
||||
casper.thenClick('.button-save').waitForResource(/posts/, function then() {
|
||||
test.assertExists('.notification-success', 'got success notification');
|
||||
});
|
||||
|
||||
casper.thenOpen(url + 'ghost/content/', function then() {
|
||||
test.assertEquals(casper.getCurrentUrl(), url + "ghost/content/", "Ghost doesn't require login this time");
|
||||
});
|
||||
|
||||
casper.then(function then() {
|
||||
test.assertEvalEquals(function () {
|
||||
return document.querySelector('.content-list-content li').className;
|
||||
}, "active", "first item is active");
|
||||
|
||||
test.assertSelectorHasText(".content-list-content li:first-child h3", 'Test Draft Post', "first item is the post we created");
|
||||
});
|
||||
|
||||
casper.thenClick('.post-edit').waitForResource(/editor/, function then() {
|
||||
test.assertUrlMatch(/editor/, "Ghost doesn't require login this time");
|
||||
});
|
||||
|
||||
casper.thenClick('.button-save').waitForResource(/posts/, function then() {
|
||||
test.assertExists('.notification-success', 'got success notification');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: test publishing, editing, republishing, unpublishing etc
|
||||
//casper.test.begin("Ghost edit published flow works correctly", 6, function suite(test) {
|
||||
//
|
||||
// casper.test.filename = "flow_test.png";
|
||||
//
|
||||
//
|
||||
//});
|
98
core/test/functional/admin/07_logout_test.js
Normal file
98
core/test/functional/admin/07_logout_test.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Tests logging out and attempting to sign up
|
||||
*/
|
||||
|
||||
/*globals casper, __utils__, url, testPost, falseUser, email */
|
||||
casper.test.begin("Ghost logout works correctly", 2, function suite(test) {
|
||||
|
||||
casper.test.filename = "logout_test.png";
|
||||
|
||||
casper.start(url + "ghost/", function then() {
|
||||
test.assertEquals(casper.getCurrentUrl(), url + "ghost/", "Ghost doesn't require login this time");
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
casper.thenClick('#usermenu a').waitFor(function checkOpaque() {
|
||||
return this.evaluate(function () {
|
||||
var loginBox = document.querySelector('#usermenu .overlay.open');
|
||||
return window.getComputedStyle(loginBox).getPropertyValue('display') === "block"
|
||||
&& window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1";
|
||||
});
|
||||
});
|
||||
|
||||
casper.thenClick('.usermenu-signout a').waitForResource(/login/, function then() {
|
||||
test.assertExists('.notification-success', 'got success notification');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
|
||||
// has to be done after signing out
|
||||
casper.test.begin("Can't spam signin", 3, function suite(test) {
|
||||
|
||||
casper.test.filename = "spam_test.png";
|
||||
|
||||
casper.start(url + "ghost/signin/", function testTitle() {
|
||||
test.assertTitle("", "Ghost admin has no title");
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
casper.waitFor(function checkOpaque() {
|
||||
return this.evaluate(function () {
|
||||
var loginBox = document.querySelector('.login-box');
|
||||
return window.getComputedStyle(loginBox).getPropertyValue('display') === "block"
|
||||
&& window.getComputedStyle(loginBox).getPropertyValue('opacity') === "1";
|
||||
});
|
||||
}, function then() {
|
||||
this.fill("#login", falseUser, true);
|
||||
casper.wait(200, function doneWait() {
|
||||
this.fill("#login", falseUser, true);
|
||||
});
|
||||
|
||||
});
|
||||
casper.wait(200, function doneWait() {
|
||||
this.echo("I've waited for 1 seconds.");
|
||||
});
|
||||
|
||||
casper.then(function testForErrorMessage() {
|
||||
test.assertExists('.notification-error', 'got error notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
||||
|
||||
casper.test.begin("Ghost signup fails properly", 5, function suite(test) {
|
||||
|
||||
casper.test.filename = "signup_test.png";
|
||||
|
||||
casper.start(url + "ghost/signup/", function then() {
|
||||
test.assertEquals(casper.getCurrentUrl(), url + "ghost/signup/", "Reached signup page");
|
||||
}).viewport(1280, 1024);
|
||||
|
||||
casper.then(function signupWithShortPassword() {
|
||||
this.fill("#register", {email: email, password: 'test'}, true);
|
||||
});
|
||||
|
||||
// should now throw a short password error
|
||||
casper.waitForResource(/signup/, function () {
|
||||
test.assertExists('.notification-error', 'got error notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
});
|
||||
|
||||
casper.then(function signupWithLongPassword() {
|
||||
this.fill("#register", {email: email, password: 'testing1234'}, true);
|
||||
});
|
||||
|
||||
// should now throw a 1 user only error
|
||||
casper.waitForResource(/signup/, function () {
|
||||
test.assertExists('.notification-error', 'got error notification');
|
||||
test.assertSelectorDoesntHaveText('.notification-error', '[object Object]');
|
||||
});
|
||||
|
||||
casper.run(function () {
|
||||
test.done();
|
||||
});
|
||||
});
|
|
@ -3,7 +3,7 @@ var _ = require('underscore'),
|
|||
should = require('should'),
|
||||
helpers = require('./helpers'),
|
||||
errors = require('../../server/errorHandling'),
|
||||
Models = require('../../server/models');
|
||||
Models = require('../../server/models'),
|
||||
when = require('when');
|
||||
|
||||
describe('User Model', function run() {
|
||||
|
@ -39,7 +39,7 @@ describe('User Model', function run() {
|
|||
should.exist(createdUser);
|
||||
createdUser.has('uuid').should.equal(true);
|
||||
createdUser.attributes.password.should.not.equal(userData.password, "password was hashed");
|
||||
createdUser.attributes.email_address.should.eql(userData.email_address, "email address corred");
|
||||
createdUser.attributes.email_address.should.eql(userData.email_address, "email address correct");
|
||||
|
||||
done();
|
||||
}).then(null, done);
|
||||
|
|
Loading…
Add table
Reference in a new issue