mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-28 21:33:24 +03:00
parent
cbb59a57db
commit
bc7906a7b2
@ -1,6 +1,6 @@
|
|||||||
// This file defines everything that helpers "require"
|
// This file defines everything that helpers "require"
|
||||||
// With the exception of modules like lodash, Bluebird
|
// With the exception of modules like lodash, Bluebird
|
||||||
// We can later refactor to enforce this something like we did in apps
|
// We can later refactor to enforce this something like we do in apps
|
||||||
var hbs = require('../services/themes/engine'),
|
var hbs = require('../services/themes/engine'),
|
||||||
settingsCache = require('../../server/services/settings/cache'),
|
settingsCache = require('../../server/services/settings/cache'),
|
||||||
config = require('../../server/config');
|
config = require('../../server/config');
|
||||||
|
2
core/frontend/services/routing/bootstrap.js
vendored
2
core/frontend/services/routing/bootstrap.js
vendored
@ -58,7 +58,7 @@ module.exports.init = (options = {start: false}) => {
|
|||||||
* 3. Taxonomies: Stronger than collections, because it's an inbuilt feature.
|
* 3. Taxonomies: Stronger than collections, because it's an inbuilt feature.
|
||||||
* 4. Collections
|
* 4. Collections
|
||||||
* 5. Static Pages: Weaker than collections, because we first try to find a post slug and fallback to lookup a static page.
|
* 5. Static Pages: Weaker than collections, because we first try to find a post slug and fallback to lookup a static page.
|
||||||
* 6. Internal Apps: Weakest
|
* 6. Apps: Weakest
|
||||||
*/
|
*/
|
||||||
module.exports.start = (apiVersion) => {
|
module.exports.start = (apiVersion) => {
|
||||||
const RESOURCE_CONFIG = require(`./config/${apiVersion}`);
|
const RESOURCE_CONFIG = require(`./config/${apiVersion}`);
|
||||||
|
@ -4,7 +4,8 @@ const common = require('../../lib/common');
|
|||||||
const allowedTypes = {
|
const allowedTypes = {
|
||||||
post: models.Post,
|
post: models.Post,
|
||||||
tag: models.Tag,
|
tag: models.Tag,
|
||||||
user: models.User
|
user: models.User,
|
||||||
|
app: models.App
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -4,7 +4,8 @@ const common = require('../../lib/common');
|
|||||||
const allowedTypes = {
|
const allowedTypes = {
|
||||||
post: models.Post,
|
post: models.Post,
|
||||||
tag: models.Tag,
|
tag: models.Tag,
|
||||||
user: models.User
|
user: models.User,
|
||||||
|
app: models.App
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
const debug = require('ghost-ignition').debug('importer:settings');
|
const debug = require('ghost-ignition').debug('importer:settings'),
|
||||||
const Promise = require('bluebird');
|
Promise = require('bluebird'),
|
||||||
const _ = require('lodash');
|
_ = require('lodash'),
|
||||||
const BaseImporter = require('./base');
|
BaseImporter = require('./base'),
|
||||||
const models = require('../../../../models');
|
models = require('../../../../models'),
|
||||||
const defaultSettings = require('../../../schema').defaultSettings;
|
defaultSettings = require('../../../schema').defaultSettings,
|
||||||
const labsDefaults = JSON.parse(defaultSettings.blog.labs.defaultValue);
|
labsDefaults = JSON.parse(defaultSettings.blog.labs.defaultValue);
|
||||||
const deprecatedSettings = ['active_apps', 'installed_apps'];
|
|
||||||
|
|
||||||
const isFalse = (value) => {
|
const isFalse = (value) => {
|
||||||
// Catches false, null, undefined, empty string
|
// Catches false, null, undefined, empty string
|
||||||
@ -66,9 +65,27 @@ class SettingsImporter extends BaseImporter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't import any old, deprecated settings
|
const activeApps = _.find(this.dataToImport, {key: 'active_apps'});
|
||||||
|
const installedApps = _.find(this.dataToImport, {key: 'installed_apps'});
|
||||||
|
|
||||||
|
const hasValueEntries = (setting = {}) => {
|
||||||
|
try {
|
||||||
|
return JSON.parse(setting.value || '[]').length !== 0;
|
||||||
|
} catch (e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hasValueEntries(activeApps) || hasValueEntries(installedApps)) {
|
||||||
|
this.problems.push({
|
||||||
|
message: 'Old settings for apps were not imported',
|
||||||
|
help: this.modelName,
|
||||||
|
context: JSON.stringify({activeApps, installedApps})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.dataToImport = _.filter(this.dataToImport, (data) => {
|
this.dataToImport = _.filter(this.dataToImport, (data) => {
|
||||||
return !_.includes(deprecatedSettings, data.key);
|
return data.key !== 'active_apps' && data.key !== 'installed_apps';
|
||||||
});
|
});
|
||||||
|
|
||||||
const permalinks = _.find(this.dataToImport, {key: 'permalinks'});
|
const permalinks = _.find(this.dataToImport, {key: 'permalinks'});
|
||||||
|
@ -152,7 +152,7 @@ module.exports = {
|
|||||||
maxlength: 50,
|
maxlength: 50,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
defaultTo: 'core',
|
defaultTo: 'core',
|
||||||
validations: {isIn: [['core', 'blog', 'theme', 'private', 'members', 'bulk_email']]}
|
validations: {isIn: [['core', 'blog', 'theme', 'app', 'plugin', 'private', 'members', 'bulk_email']]}
|
||||||
},
|
},
|
||||||
created_at: {type: 'dateTime', nullable: false},
|
created_at: {type: 'dateTime', nullable: false},
|
||||||
created_by: {type: 'string', maxlength: 24, nullable: false},
|
created_by: {type: 'string', maxlength: 24, nullable: false},
|
||||||
|
@ -30,6 +30,7 @@ function initialiseServices() {
|
|||||||
routing.bootstrap.start(themeService.getApiVersion());
|
routing.bootstrap.start(themeService.getApiVersion());
|
||||||
|
|
||||||
const permissions = require('./services/permissions'),
|
const permissions = require('./services/permissions'),
|
||||||
|
apps = require('./services/apps'),
|
||||||
xmlrpc = require('./services/xmlrpc'),
|
xmlrpc = require('./services/xmlrpc'),
|
||||||
slack = require('./services/slack'),
|
slack = require('./services/slack'),
|
||||||
{mega} = require('./services/mega'),
|
{mega} = require('./services/mega'),
|
||||||
@ -45,6 +46,7 @@ function initialiseServices() {
|
|||||||
slack.listen(),
|
slack.listen(),
|
||||||
mega.listen(),
|
mega.listen(),
|
||||||
webhooks.listen(),
|
webhooks.listen(),
|
||||||
|
apps.init(),
|
||||||
scheduling.init({
|
scheduling.init({
|
||||||
schedulerUrl: config.get('scheduling').schedulerUrl,
|
schedulerUrl: config.get('scheduling').schedulerUrl,
|
||||||
active: config.get('scheduling').active,
|
active: config.get('scheduling').active,
|
||||||
@ -55,7 +57,7 @@ function initialiseServices() {
|
|||||||
contentPath: config.getContentPath('scheduling')
|
contentPath: config.getContentPath('scheduling')
|
||||||
})
|
})
|
||||||
).then(function () {
|
).then(function () {
|
||||||
debug('XMLRPC, Slack, MEGA, Webhooks, Scheduling, Permissions done');
|
debug('XMLRPC, Slack, MEGA, Webhooks, Apps, Scheduling, Permissions done');
|
||||||
|
|
||||||
// Initialise analytics events
|
// Initialise analytics events
|
||||||
if (config.get('segment:key')) {
|
if (config.get('segment:key')) {
|
||||||
|
@ -4,7 +4,7 @@ var _ = require('lodash'),
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* ### Filter Packages
|
* ### Filter Packages
|
||||||
* Normalizes packages read by read-packages so that the themes module can use them.
|
* Normalizes packages read by read-packages so that the apps and themes modules can use them.
|
||||||
* Iterates over each package and return an array of objects which are simplified representations of the package
|
* Iterates over each package and return an array of objects which are simplified representations of the package
|
||||||
* with 3 properties:
|
* with 3 properties:
|
||||||
* - `name` - the package name
|
* - `name` - the package name
|
||||||
@ -17,10 +17,10 @@ var _ = require('lodash'),
|
|||||||
*
|
*
|
||||||
* @param {object} packages as returned by read-packages
|
* @param {object} packages as returned by read-packages
|
||||||
* @param {array/string} active as read from the settings object
|
* @param {array/string} active as read from the settings object
|
||||||
* @returns {Array} of objects with useful info about themes
|
* @returns {Array} of objects with useful info about apps / themes
|
||||||
*/
|
*/
|
||||||
filterPackages = function filterPackages(packages, active) {
|
filterPackages = function filterPackages(packages, active) {
|
||||||
// turn active into an array if it isn't one, so this function can deal with lists and one-offs
|
// turn active into an array (so themes and apps can be checked the same)
|
||||||
if (!Array.isArray(active)) {
|
if (!Array.isArray(active)) {
|
||||||
active = [active];
|
active = [active];
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
*
|
*
|
||||||
* Ghost has / is in the process of gaining support for several different types of sub-packages:
|
* Ghost has / is in the process of gaining support for several different types of sub-packages:
|
||||||
* - Themes: have always been packages, but we're going to lean more heavily on npm & package.json in future
|
* - Themes: have always been packages, but we're going to lean more heavily on npm & package.json in future
|
||||||
* - Adapters: replace fundamental pieces like storage, will become npm modules
|
* - Adapters: an early version of apps, replace fundamental pieces like storage, will become npm modules
|
||||||
|
* - Apps: plugins that can be installed whilst Ghost is running & modify behaviour
|
||||||
|
* - More?
|
||||||
*
|
*
|
||||||
* These utils facilitate loading, reading, managing etc, packages from the file system.
|
* These utils facilitate loading, reading, managing etc, packages from the file system.
|
||||||
*/
|
*/
|
||||||
|
20
core/server/models/app-field.js
Normal file
20
core/server/models/app-field.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
var ghostBookshelf = require('./base'),
|
||||||
|
AppField,
|
||||||
|
AppFields;
|
||||||
|
|
||||||
|
AppField = ghostBookshelf.Model.extend({
|
||||||
|
tableName: 'app_fields',
|
||||||
|
|
||||||
|
post: function post() {
|
||||||
|
return this.morphOne('Post', 'relatable');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AppFields = ghostBookshelf.Collection.extend({
|
||||||
|
model: AppField
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
AppField: ghostBookshelf.model('AppField', AppField),
|
||||||
|
AppFields: ghostBookshelf.collection('AppFields', AppFields)
|
||||||
|
};
|
20
core/server/models/app-setting.js
Normal file
20
core/server/models/app-setting.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
var ghostBookshelf = require('./base'),
|
||||||
|
AppSetting,
|
||||||
|
AppSettings;
|
||||||
|
|
||||||
|
AppSetting = ghostBookshelf.Model.extend({
|
||||||
|
tableName: 'app_settings',
|
||||||
|
|
||||||
|
app: function app() {
|
||||||
|
return this.belongsTo('App');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
AppSettings = ghostBookshelf.Collection.extend({
|
||||||
|
model: AppSetting
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
AppSetting: ghostBookshelf.model('AppSetting', AppSetting),
|
||||||
|
AppSettings: ghostBookshelf.collection('AppSettings', AppSettings)
|
||||||
|
};
|
60
core/server/models/app.js
Normal file
60
core/server/models/app.js
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
var ghostBookshelf = require('./base'),
|
||||||
|
App,
|
||||||
|
Apps;
|
||||||
|
|
||||||
|
App = ghostBookshelf.Model.extend({
|
||||||
|
tableName: 'apps',
|
||||||
|
|
||||||
|
onSaving: function onSaving(newPage, attr, options) {
|
||||||
|
var self = this;
|
||||||
|
|
||||||
|
ghostBookshelf.Model.prototype.onSaving.apply(this, arguments);
|
||||||
|
|
||||||
|
if (this.hasChanged('slug') || !this.get('slug')) {
|
||||||
|
// Pass the new slug through the generator to strip illegal characters, detect duplicates
|
||||||
|
return ghostBookshelf.Model.generateSlug(App, this.get('slug') || this.get('name'),
|
||||||
|
{transacting: options.transacting})
|
||||||
|
.then(function then(slug) {
|
||||||
|
self.set({slug: slug});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
permissions: function permissions() {
|
||||||
|
return this.belongsToMany('Permission', 'permissions_apps');
|
||||||
|
},
|
||||||
|
|
||||||
|
settings: function settings() {
|
||||||
|
return this.belongsToMany('AppSetting', 'app_settings');
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
/**
|
||||||
|
* Returns an array of keys permitted in a method's `options` hash, depending on the current method.
|
||||||
|
* @param {String} methodName The name of the method to check valid options for.
|
||||||
|
* @return {Array} Keys allowed in the `options` hash of the model's method.
|
||||||
|
*/
|
||||||
|
permittedOptions: function permittedOptions(methodName) {
|
||||||
|
var options = ghostBookshelf.Model.permittedOptions.call(this, methodName),
|
||||||
|
|
||||||
|
// whitelists for the `options` hash argument on methods, by method name.
|
||||||
|
// these are the only options that can be passed to Bookshelf / Knex.
|
||||||
|
validOptions = {
|
||||||
|
findOne: ['withRelated']
|
||||||
|
};
|
||||||
|
|
||||||
|
if (validOptions[methodName]) {
|
||||||
|
options = options.concat(validOptions[methodName]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Apps = ghostBookshelf.Collection.extend({
|
||||||
|
model: App
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
App: ghostBookshelf.model('App', App),
|
||||||
|
Apps: ghostBookshelf.collection('Apps', Apps)
|
||||||
|
};
|
@ -3,9 +3,8 @@
|
|||||||
// several basic behaviours such as UUIDs, as well as a set of Data methods for accessing information from the database.
|
// several basic behaviours such as UUIDs, as well as a set of Data methods for accessing information from the database.
|
||||||
//
|
//
|
||||||
// The models are internal to Ghost, only the API and some internal functions such as migration and import/export
|
// The models are internal to Ghost, only the API and some internal functions such as migration and import/export
|
||||||
// accesses the models directly.
|
// accesses the models directly. All other parts of Ghost, including the blog frontend, admin UI, and apps are only
|
||||||
|
// allowed to access data via the API.
|
||||||
// All other parts of Ghost, including the frontend & admin UI are only allowed to access data via the API.
|
|
||||||
const _ = require('lodash'),
|
const _ = require('lodash'),
|
||||||
bookshelf = require('bookshelf'),
|
bookshelf = require('bookshelf'),
|
||||||
moment = require('moment'),
|
moment = require('moment'),
|
||||||
|
@ -15,6 +15,9 @@ require('./base/listeners');
|
|||||||
exports = module.exports;
|
exports = module.exports;
|
||||||
|
|
||||||
models = [
|
models = [
|
||||||
|
'app-field',
|
||||||
|
'app-setting',
|
||||||
|
'app',
|
||||||
'permission',
|
'permission',
|
||||||
'post',
|
'post',
|
||||||
'role',
|
'role',
|
||||||
|
@ -42,11 +42,11 @@ Invite = ghostBookshelf.Model.extend({
|
|||||||
return ghostBookshelf.Model.add.call(this, data, options);
|
return ghostBookshelf.Model.add.call(this, data, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
permissible(inviteModel, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission) {
|
permissible(inviteModel, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission, hasApiKeyPermission) {
|
||||||
const isAdd = (action === 'add');
|
const isAdd = (action === 'add');
|
||||||
|
|
||||||
if (!isAdd) {
|
if (!isAdd) {
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasAppPermission && hasApiKeyPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,7 +86,7 @@ Invite = ghostBookshelf.Model.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasAppPermission && hasApiKeyPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +33,10 @@ Permission = ghostBookshelf.Model.extend({
|
|||||||
|
|
||||||
users: function users() {
|
users: function users() {
|
||||||
return this.belongsToMany('User');
|
return this.belongsToMany('User');
|
||||||
|
},
|
||||||
|
|
||||||
|
apps: function apps() {
|
||||||
|
return this.belongsToMany('App');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -934,7 +934,7 @@ Post = ghostBookshelf.Model.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
// NOTE: the `authors` extension is the parent of the post model. It also has a permissible function.
|
// NOTE: the `authors` extension is the parent of the post model. It also has a permissible function.
|
||||||
permissible: function permissible(postModel, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission) {
|
permissible: function permissible(postModel, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission, hasApiKeyPermission) {
|
||||||
let isContributor;
|
let isContributor;
|
||||||
let isOwner;
|
let isOwner;
|
||||||
let isAdmin;
|
let isAdmin;
|
||||||
@ -989,7 +989,7 @@ Post = ghostBookshelf.Model.extend({
|
|||||||
excludedAttrs.push('tags');
|
excludedAttrs.push('tags');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return Promise.resolve({excludedAttrs});
|
return Promise.resolve({excludedAttrs});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -331,7 +331,7 @@ module.exports.extendModel = function extendModel(Post, Posts, ghostBookshelf) {
|
|||||||
return destroyPost();
|
return destroyPost();
|
||||||
},
|
},
|
||||||
|
|
||||||
permissible: function permissible(postModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission) {
|
permissible: function permissible(postModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission, hasApiKeyPermission) {
|
||||||
var self = this,
|
var self = this,
|
||||||
postModel = postModelOrId,
|
postModel = postModelOrId,
|
||||||
origArgs, isContributor, isAuthor, isEdit, isAdd, isDestroy;
|
origArgs, isContributor, isAuthor, isEdit, isAdd, isDestroy;
|
||||||
@ -420,7 +420,7 @@ module.exports.extendModel = function extendModel(Post, Posts, ghostBookshelf) {
|
|||||||
hasUserPermission = hasUserPermission || isPrimaryAuthor();
|
hasUserPermission = hasUserPermission || isPrimaryAuthor();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return Post.permissible.call(
|
return Post.permissible.call(
|
||||||
this,
|
this,
|
||||||
postModelOrId,
|
postModelOrId,
|
||||||
@ -428,6 +428,7 @@ module.exports.extendModel = function extendModel(Post, Posts, ghostBookshelf) {
|
|||||||
unsafeAttrs,
|
unsafeAttrs,
|
||||||
loadedPermissions,
|
loadedPermissions,
|
||||||
hasUserPermission,
|
hasUserPermission,
|
||||||
|
hasAppPermission,
|
||||||
hasApiKeyPermission
|
hasApiKeyPermission
|
||||||
).then(({excludedAttrs}) => {
|
).then(({excludedAttrs}) => {
|
||||||
// @TODO: we need a concept for making a diff between incoming authors and existing authors
|
// @TODO: we need a concept for making a diff between incoming authors and existing authors
|
||||||
|
@ -50,7 +50,7 @@ Role = ghostBookshelf.Model.extend({
|
|||||||
return options;
|
return options;
|
||||||
},
|
},
|
||||||
|
|
||||||
permissible: function permissible(roleModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission) {
|
permissible: function permissible(roleModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission, hasApiKeyPermission) {
|
||||||
// If we passed in an id instead of a model, get the model
|
// If we passed in an id instead of a model, get the model
|
||||||
// then check the permissions
|
// then check the permissions
|
||||||
if (_.isNumber(roleModelOrId) || _.isString(roleModelOrId)) {
|
if (_.isNumber(roleModelOrId) || _.isString(roleModelOrId)) {
|
||||||
@ -95,7 +95,7 @@ Role = ghostBookshelf.Model.extend({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasAppPermission && hasApiKeyPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ Settings = ghostBookshelf.Model.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
permissible: function permissible(modelId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission) {
|
permissible: function permissible(modelId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission, hasApiKeyPermission) {
|
||||||
let isEdit = (action === 'edit');
|
let isEdit = (action === 'edit');
|
||||||
let isOwner;
|
let isOwner;
|
||||||
|
|
||||||
@ -271,7 +271,7 @@ Settings = ghostBookshelf.Model.extend({
|
|||||||
hasUserPermission = isOwner;
|
hasUserPermission = isOwner;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -648,7 +648,7 @@ User = ghostBookshelf.Model.extend({
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
permissible: function permissible(userModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission) {
|
permissible: function permissible(userModelOrId, action, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission, hasApiKeyPermission) {
|
||||||
var self = this,
|
var self = this,
|
||||||
userModel = userModelOrId,
|
userModel = userModelOrId,
|
||||||
origArgs;
|
origArgs;
|
||||||
@ -738,7 +738,7 @@ User = ghostBookshelf.Model.extend({
|
|||||||
.then((owner) => {
|
.then((owner) => {
|
||||||
// CASE: owner can assign role to any user
|
// CASE: owner can assign role to any user
|
||||||
if (context.user === owner.id) {
|
if (context.user === owner.id) {
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -760,7 +760,7 @@ User = ghostBookshelf.Model.extend({
|
|||||||
// e.g. admin can assign admin role to a user, but not owner
|
// e.g. admin can assign admin role to a user, but not owner
|
||||||
return permissions.canThis(context).assign.role(role)
|
return permissions.canThis(context).assign.role(role)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -770,7 +770,7 @@ User = ghostBookshelf.Model.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -780,7 +780,7 @@ User = ghostBookshelf.Model.extend({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
21
core/server/services/apps/index.js
Normal file
21
core/server/services/apps/index.js
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
const debug = require('ghost-ignition').debug('services:apps');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
const common = require('../../lib/common');
|
||||||
|
const config = require('../../config');
|
||||||
|
const loader = require('./loader');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
init: function () {
|
||||||
|
debug('init begin');
|
||||||
|
const appsToLoad = config.get('apps:internal');
|
||||||
|
|
||||||
|
return Promise.map(appsToLoad, appName => loader.activateAppByName(appName))
|
||||||
|
.catch(function (err) {
|
||||||
|
common.logging.error(new common.errors.GhostError({
|
||||||
|
err: err,
|
||||||
|
context: common.i18n.t('errors.apps.appWillNotBeLoaded.error'),
|
||||||
|
help: common.i18n.t('errors.apps.appWillNotBeLoaded.help')
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
45
core/server/services/apps/loader.js
Normal file
45
core/server/services/apps/loader.js
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
const path = require('path');
|
||||||
|
const _ = require('lodash');
|
||||||
|
const Promise = require('bluebird');
|
||||||
|
const common = require('../../lib/common');
|
||||||
|
const config = require('../../config');
|
||||||
|
const Proxy = require('./proxy');
|
||||||
|
|
||||||
|
// Get the full path to an app by name
|
||||||
|
function getAppAbsolutePath(name) {
|
||||||
|
return path.join(config.get('paths').internalAppPath, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadApp(name) {
|
||||||
|
return require(getAppAbsolutePath(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAppByName(name) {
|
||||||
|
// Grab the app class to instantiate
|
||||||
|
const AppClass = loadApp(name);
|
||||||
|
const proxy = Proxy.getInstance();
|
||||||
|
|
||||||
|
// Check for an actual class, otherwise just use whatever was returned
|
||||||
|
const app = _.isFunction(AppClass) ? new AppClass(proxy) : AppClass;
|
||||||
|
|
||||||
|
return {
|
||||||
|
app,
|
||||||
|
proxy
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
// Activate a app and return it
|
||||||
|
activateAppByName: function (name) {
|
||||||
|
const {app, proxy} = getAppByName(name);
|
||||||
|
|
||||||
|
// Check for an activate() method on the app.
|
||||||
|
if (!_.isFunction(app.activate)) {
|
||||||
|
return Promise.reject(new Error(common.i18n.t('errors.apps.noActivateMethodLoadingApp.error', {name: name})));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wrapping the activate() with a when because it's possible
|
||||||
|
// to not return a promise from it.
|
||||||
|
return Promise.resolve(app.activate(proxy)).return(app);
|
||||||
|
}
|
||||||
|
};
|
18
core/server/services/apps/proxy.js
Normal file
18
core/server/services/apps/proxy.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
const helpers = require('../../../frontend/helpers/register');
|
||||||
|
const routingService = require('../../../frontend/services/routing');
|
||||||
|
|
||||||
|
module.exports.getInstance = function getInstance() {
|
||||||
|
const appRouter = routingService.registry.getRouter('appRouter');
|
||||||
|
|
||||||
|
return {
|
||||||
|
helpers: {
|
||||||
|
register: helpers.registerThemeHelper.bind(helpers),
|
||||||
|
registerAsync: helpers.registerAsyncThemeHelper.bind(helpers)
|
||||||
|
},
|
||||||
|
// Expose the route service...
|
||||||
|
routeService: {
|
||||||
|
// This allows for mounting an entirely new Router at a path...
|
||||||
|
registerRouter: appRouter.mountRouter.bind(appRouter)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
@ -50,8 +50,10 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, c
|
|||||||
// Iterate through the user permissions looking for an affirmation
|
// Iterate through the user permissions looking for an affirmation
|
||||||
var userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null,
|
var userPermissions = loadedPermissions.user ? loadedPermissions.user.permissions : null,
|
||||||
apiKeyPermissions = loadedPermissions.apiKey ? loadedPermissions.apiKey.permissions : null,
|
apiKeyPermissions = loadedPermissions.apiKey ? loadedPermissions.apiKey.permissions : null,
|
||||||
|
appPermissions = loadedPermissions.app ? loadedPermissions.app.permissions : null,
|
||||||
hasUserPermission,
|
hasUserPermission,
|
||||||
hasApiKeyPermission,
|
hasApiKeyPermission,
|
||||||
|
hasAppPermission,
|
||||||
checkPermission = function (perm) {
|
checkPermission = function (perm) {
|
||||||
var permObjId;
|
var permObjId;
|
||||||
|
|
||||||
@ -89,14 +91,20 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, c
|
|||||||
hasApiKeyPermission = _.some(apiKeyPermissions, checkPermission);
|
hasApiKeyPermission = _.some(apiKeyPermissions, checkPermission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check app permissions if they were passed
|
||||||
|
hasAppPermission = true;
|
||||||
|
if (!_.isNull(appPermissions)) {
|
||||||
|
hasAppPermission = _.some(appPermissions, checkPermission);
|
||||||
|
}
|
||||||
|
|
||||||
// Offer a chance for the TargetModel to override the results
|
// Offer a chance for the TargetModel to override the results
|
||||||
if (TargetModel && _.isFunction(TargetModel.permissible)) {
|
if (TargetModel && _.isFunction(TargetModel.permissible)) {
|
||||||
return TargetModel.permissible(
|
return TargetModel.permissible(
|
||||||
modelId, actType, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasApiKeyPermission
|
modelId, actType, context, unsafeAttrs, loadedPermissions, hasUserPermission, hasAppPermission, hasApiKeyPermission
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasUserPermission && hasApiKeyPermission) {
|
if (hasUserPermission && hasApiKeyPermission && hasAppPermission) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +120,7 @@ CanThisResult.prototype.beginCheck = function (context) {
|
|||||||
var self = this,
|
var self = this,
|
||||||
userPermissionLoad,
|
userPermissionLoad,
|
||||||
apiKeyPermissionLoad,
|
apiKeyPermissionLoad,
|
||||||
|
appPermissionLoad,
|
||||||
permissionsLoad;
|
permissionsLoad;
|
||||||
|
|
||||||
// Get context.user, context.api_key and context.app
|
// Get context.user, context.api_key and context.app
|
||||||
@ -137,11 +146,20 @@ CanThisResult.prototype.beginCheck = function (context) {
|
|||||||
apiKeyPermissionLoad = Promise.resolve(null);
|
apiKeyPermissionLoad = Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Kick off loading of app permissions if necessary
|
||||||
|
if (context.app) {
|
||||||
|
appPermissionLoad = providers.app(context.app);
|
||||||
|
} else {
|
||||||
|
// Resolve null if no context.app
|
||||||
|
appPermissionLoad = Promise.resolve(null);
|
||||||
|
}
|
||||||
|
|
||||||
// Wait for both user and app permissions to load
|
// Wait for both user and app permissions to load
|
||||||
permissionsLoad = Promise.all([userPermissionLoad, apiKeyPermissionLoad]).then(function (result) {
|
permissionsLoad = Promise.all([userPermissionLoad, apiKeyPermissionLoad, appPermissionLoad]).then(function (result) {
|
||||||
return {
|
return {
|
||||||
user: result[0],
|
user: result[0],
|
||||||
apiKey: result[1]
|
apiKey: result[1],
|
||||||
|
app: result[2]
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -3,14 +3,16 @@
|
|||||||
*
|
*
|
||||||
* Utility function, to expand strings out into objects.
|
* Utility function, to expand strings out into objects.
|
||||||
* @param {Object|String} context
|
* @param {Object|String} context
|
||||||
* @return {{internal: boolean, external: boolean, user: integer|null, public: boolean, api_key: Object|null}}
|
* @return {{internal: boolean, external: boolean, user: integer|null, app: integer|null, public: boolean, api_key: Object|null}}
|
||||||
*/
|
*/
|
||||||
module.exports = function parseContext(context) {
|
module.exports = function parseContext(context) {
|
||||||
|
// Parse what's passed to canThis.beginCheck for standard user and app scopes
|
||||||
var parsed = {
|
var parsed = {
|
||||||
internal: false,
|
internal: false,
|
||||||
external: false,
|
external: false,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
integration: null,
|
integration: null,
|
||||||
public: true
|
public: true
|
||||||
};
|
};
|
||||||
@ -37,5 +39,10 @@ module.exports = function parseContext(context) {
|
|||||||
parsed.public = (context.api_key.type === 'content');
|
parsed.public = (context.api_key.type === 'content');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (context && context.app) {
|
||||||
|
parsed.app = context.app;
|
||||||
|
parsed.public = false;
|
||||||
|
}
|
||||||
|
|
||||||
return parsed;
|
return parsed;
|
||||||
};
|
};
|
||||||
|
@ -44,6 +44,17 @@ module.exports = {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
app: function (appName) {
|
||||||
|
return models.App.findOne({name: appName}, {withRelated: ['permissions']})
|
||||||
|
.then(function (foundApp) {
|
||||||
|
if (!foundApp) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return {permissions: foundApp.related('permissions').models};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
apiKey(id) {
|
apiKey(id) {
|
||||||
return models.ApiKey.findOne({id}, {withRelated: ['role', 'role.permissions']})
|
return models.ApiKey.findOne({id}, {withRelated: ['role', 'role.permissions']})
|
||||||
.then((foundApiKey) => {
|
.then((foundApiKey) => {
|
||||||
|
@ -34,6 +34,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"errors": {
|
"errors": {
|
||||||
|
"apps": {
|
||||||
|
"appWillNotBeLoaded": {
|
||||||
|
"error": "The app will not be loaded",
|
||||||
|
"help": "Check with the app creator, or read the app documentation for more details on app requirements"
|
||||||
|
},
|
||||||
|
"noActivateMethodLoadingApp": {
|
||||||
|
"error": "Error loading app named {name}; no activate() method defined."
|
||||||
|
},
|
||||||
|
"mustProvideAppName": {
|
||||||
|
"error": "Must provide an app name for api context"
|
||||||
|
}
|
||||||
|
},
|
||||||
"middleware": {
|
"middleware": {
|
||||||
"api": {
|
"api": {
|
||||||
"versionMismatch": "Client request for {clientVersion} does not match server version {serverVersion}."
|
"versionMismatch": "Client request for {clientVersion} does not match server version {serverVersion}."
|
||||||
|
@ -47,7 +47,7 @@ module.exports = function setupParentApp(options = {}) {
|
|||||||
// This sets global res.locals which are needed everywhere
|
// This sets global res.locals which are needed everywhere
|
||||||
parentApp.use(shared.middlewares.ghostLocals);
|
parentApp.use(shared.middlewares.ghostLocals);
|
||||||
|
|
||||||
// Mount the express apps on the parentApp
|
// Mount the apps on the parentApp
|
||||||
|
|
||||||
const adminHost = config.get('admin:url') ? (new URL(config.get('admin:url')).hostname) : '';
|
const adminHost = config.get('admin:url') ? (new URL(config.get('admin:url')).hostname) : '';
|
||||||
const frontendHost = new URL(config.get('url')).hostname;
|
const frontendHost = new URL(config.get('url')).hostname;
|
||||||
|
@ -7,6 +7,7 @@ const common = require('../../lib/common');
|
|||||||
|
|
||||||
// App requires
|
// App requires
|
||||||
const config = require('../../config');
|
const config = require('../../config');
|
||||||
|
const apps = require('../../services/apps');
|
||||||
const constants = require('../../lib/constants');
|
const constants = require('../../lib/constants');
|
||||||
const storage = require('../../adapters/storage');
|
const storage = require('../../adapters/storage');
|
||||||
const urlService = require('../../../frontend/services/url');
|
const urlService = require('../../../frontend/services/url');
|
||||||
@ -155,7 +156,7 @@ module.exports = function setupSiteApp(options = {}) {
|
|||||||
siteApp.use(shared.middlewares.servePublicFile('robots.txt', 'text/plain', constants.ONE_HOUR_S));
|
siteApp.use(shared.middlewares.servePublicFile('robots.txt', 'text/plain', constants.ONE_HOUR_S));
|
||||||
|
|
||||||
// setup middleware for internal apps
|
// setup middleware for internal apps
|
||||||
// @TODO: refactor this to be a proper app middleware hook for internal apps
|
// @TODO: refactor this to be a proper app middleware hook for internal & external apps
|
||||||
config.get('apps:internal').forEach((appName) => {
|
config.get('apps:internal').forEach((appName) => {
|
||||||
const app = require(path.join(config.get('paths').internalAppPath, appName));
|
const app = require(path.join(config.get('paths').internalAppPath, appName));
|
||||||
|
|
||||||
@ -210,6 +211,9 @@ module.exports.reload = () => {
|
|||||||
router = siteRoutes({start: themeService.getApiVersion()});
|
router = siteRoutes({start: themeService.getApiVersion()});
|
||||||
Object.setPrototypeOf(SiteRouter, router);
|
Object.setPrototypeOf(SiteRouter, router);
|
||||||
|
|
||||||
|
// re-initialse apps (register app routers, because we have re-initialised the site routers)
|
||||||
|
apps.init();
|
||||||
|
|
||||||
// connect routers and resources again
|
// connect routers and resources again
|
||||||
urlService.queue.start({
|
urlService.queue.start({
|
||||||
event: 'init',
|
event: 'init',
|
||||||
|
@ -5,6 +5,7 @@ const should = require('should'),
|
|||||||
testUtils = require('../../utils'),
|
testUtils = require('../../utils'),
|
||||||
configUtils = require('../../utils/configUtils'),
|
configUtils = require('../../utils/configUtils'),
|
||||||
urlUtils = require('../../utils/urlUtils'),
|
urlUtils = require('../../utils/urlUtils'),
|
||||||
|
appsService = require('../../../server/services/apps'),
|
||||||
frontendSettingsService = require('../../../frontend/services/settings'),
|
frontendSettingsService = require('../../../frontend/services/settings'),
|
||||||
themeService = require('../../../frontend/services/themes'),
|
themeService = require('../../../frontend/services/themes'),
|
||||||
siteApp = require('../../../server/web/parent-app');
|
siteApp = require('../../../server/web/parent-app');
|
||||||
@ -22,7 +23,7 @@ describe('Integration - Web - Site', function () {
|
|||||||
describe('default routes.yaml', function () {
|
describe('default routes.yaml', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
testUtils.integrationTesting.urlService.resetGenerators();
|
testUtils.integrationTesting.urlService.resetGenerators();
|
||||||
testUtils.integrationTesting.defaultMocks(sinon, {amp: true});
|
testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true});
|
||||||
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
||||||
|
|
||||||
return testUtils.integrationTesting.initGhost()
|
return testUtils.integrationTesting.initGhost()
|
||||||
@ -32,6 +33,9 @@ describe('Integration - Web - Site', function () {
|
|||||||
|
|
||||||
app = siteApp({start: true});
|
app = siteApp({start: true});
|
||||||
return testUtils.integrationTesting.urlService.waitTillFinished();
|
return testUtils.integrationTesting.urlService.waitTillFinished();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return appsService.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -1717,7 +1721,7 @@ describe('Integration - Web - Site', function () {
|
|||||||
describe('default routes.yaml', function () {
|
describe('default routes.yaml', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
testUtils.integrationTesting.urlService.resetGenerators();
|
testUtils.integrationTesting.urlService.resetGenerators();
|
||||||
testUtils.integrationTesting.defaultMocks(sinon, {amp: true});
|
testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true});
|
||||||
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
||||||
|
|
||||||
return testUtils.integrationTesting.initGhost()
|
return testUtils.integrationTesting.initGhost()
|
||||||
@ -1727,6 +1731,9 @@ describe('Integration - Web - Site', function () {
|
|||||||
|
|
||||||
app = siteApp({start: true});
|
app = siteApp({start: true});
|
||||||
return testUtils.integrationTesting.urlService.waitTillFinished();
|
return testUtils.integrationTesting.urlService.waitTillFinished();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return appsService.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -3414,7 +3421,7 @@ describe('Integration - Web - Site', function () {
|
|||||||
describe('default routes.yaml', function () {
|
describe('default routes.yaml', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
testUtils.integrationTesting.urlService.resetGenerators();
|
testUtils.integrationTesting.urlService.resetGenerators();
|
||||||
testUtils.integrationTesting.defaultMocks(sinon, {amp: true});
|
testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true});
|
||||||
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
||||||
|
|
||||||
return testUtils.integrationTesting.initGhost()
|
return testUtils.integrationTesting.initGhost()
|
||||||
@ -3424,6 +3431,9 @@ describe('Integration - Web - Site', function () {
|
|||||||
|
|
||||||
app = siteApp({start: true});
|
app = siteApp({start: true});
|
||||||
return testUtils.integrationTesting.urlService.waitTillFinished();
|
return testUtils.integrationTesting.urlService.waitTillFinished();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return appsService.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -5110,7 +5120,7 @@ describe('Integration - Web - Site', function () {
|
|||||||
describe('no separate admin', function () {
|
describe('no separate admin', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
testUtils.integrationTesting.urlService.resetGenerators();
|
testUtils.integrationTesting.urlService.resetGenerators();
|
||||||
testUtils.integrationTesting.defaultMocks(sinon, {amp: true});
|
testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true});
|
||||||
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
||||||
|
|
||||||
configUtils.set('url', 'http://example.com');
|
configUtils.set('url', 'http://example.com');
|
||||||
@ -5123,6 +5133,9 @@ describe('Integration - Web - Site', function () {
|
|||||||
|
|
||||||
app = siteApp({start: true});
|
app = siteApp({start: true});
|
||||||
return testUtils.integrationTesting.urlService.waitTillFinished();
|
return testUtils.integrationTesting.urlService.waitTillFinished();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return appsService.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -5226,7 +5239,7 @@ describe('Integration - Web - Site', function () {
|
|||||||
describe('separate admin host', function () {
|
describe('separate admin host', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
testUtils.integrationTesting.urlService.resetGenerators();
|
testUtils.integrationTesting.urlService.resetGenerators();
|
||||||
testUtils.integrationTesting.defaultMocks(sinon, {amp: true});
|
testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true});
|
||||||
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
||||||
|
|
||||||
configUtils.set('url', 'http://example.com');
|
configUtils.set('url', 'http://example.com');
|
||||||
@ -5239,6 +5252,9 @@ describe('Integration - Web - Site', function () {
|
|||||||
|
|
||||||
app = siteApp({start: true});
|
app = siteApp({start: true});
|
||||||
return testUtils.integrationTesting.urlService.waitTillFinished();
|
return testUtils.integrationTesting.urlService.waitTillFinished();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return appsService.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -5384,7 +5400,7 @@ describe('Integration - Web - Site', function () {
|
|||||||
describe('separate admin host w/ admin redirects disabled', function () {
|
describe('separate admin host w/ admin redirects disabled', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
testUtils.integrationTesting.urlService.resetGenerators();
|
testUtils.integrationTesting.urlService.resetGenerators();
|
||||||
testUtils.integrationTesting.defaultMocks(sinon, {amp: true});
|
testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true});
|
||||||
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
||||||
|
|
||||||
configUtils.set('url', 'http://example.com');
|
configUtils.set('url', 'http://example.com');
|
||||||
@ -5398,6 +5414,9 @@ describe('Integration - Web - Site', function () {
|
|||||||
|
|
||||||
app = siteApp({start: true});
|
app = siteApp({start: true});
|
||||||
return testUtils.integrationTesting.urlService.waitTillFinished();
|
return testUtils.integrationTesting.urlService.waitTillFinished();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return appsService.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -5429,7 +5448,7 @@ describe('Integration - Web - Site', function () {
|
|||||||
describe('same host separate protocol', function () {
|
describe('same host separate protocol', function () {
|
||||||
before(function () {
|
before(function () {
|
||||||
testUtils.integrationTesting.urlService.resetGenerators();
|
testUtils.integrationTesting.urlService.resetGenerators();
|
||||||
testUtils.integrationTesting.defaultMocks(sinon, {amp: true});
|
testUtils.integrationTesting.defaultMocks(sinon, {amp: true, apps: true});
|
||||||
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
testUtils.integrationTesting.overrideGhostConfig(configUtils);
|
||||||
|
|
||||||
configUtils.set('url', 'http://example.com');
|
configUtils.set('url', 'http://example.com');
|
||||||
@ -5442,6 +5461,9 @@ describe('Integration - Web - Site', function () {
|
|||||||
|
|
||||||
app = siteApp({start: true});
|
app = siteApp({start: true});
|
||||||
return testUtils.integrationTesting.urlService.waitTillFinished();
|
return testUtils.integrationTesting.urlService.waitTillFinished();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
return appsService.init();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
36
core/test/unit/services/apps/proxy_spec.js
Normal file
36
core/test/unit/services/apps/proxy_spec.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
const should = require('should'),
|
||||||
|
sinon = require('sinon'),
|
||||||
|
helpers = require('../../../../frontend/helpers/register'),
|
||||||
|
AppProxy = require('../../../../server/services/apps/proxy'),
|
||||||
|
routing = require('../../../../frontend/services/routing');
|
||||||
|
|
||||||
|
describe('Apps', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
sinon.stub(routing.registry, 'getRouter').withArgs('appRouter').returns({
|
||||||
|
mountRouter: sinon.stub()
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sinon.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Proxy', function () {
|
||||||
|
it('creates a ghost proxy', function () {
|
||||||
|
var appProxy = AppProxy.getInstance('TestApp');
|
||||||
|
|
||||||
|
should.exist(appProxy.helpers);
|
||||||
|
should.exist(appProxy.helpers.register);
|
||||||
|
should.exist(appProxy.helpers.registerAsync);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows helper registration', function () {
|
||||||
|
var registerSpy = sinon.stub(helpers, 'registerThemeHelper'),
|
||||||
|
appProxy = AppProxy.getInstance('TestApp');
|
||||||
|
|
||||||
|
appProxy.helpers.register('myTestHelper', sinon.stub().returns('test result'));
|
||||||
|
|
||||||
|
registerSpy.called.should.equal(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -99,7 +99,7 @@ describe('Permissions', function () {
|
|||||||
canThisResult.destroy.user.should.be.a.Function();
|
canThisResult.destroy.user.should.be.a.Function();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Non user permissions', function () {
|
describe('Non user/app permissions', function () {
|
||||||
// TODO change to using fake models in tests!
|
// TODO change to using fake models in tests!
|
||||||
// Permissions need to be NOT fundamentally baked into Ghost, but a separate module, at some point
|
// Permissions need to be NOT fundamentally baked into Ghost, but a separate module, at some point
|
||||||
// It can depend on bookshelf, but should NOT use hard coded model knowledge.
|
// It can depend on bookshelf, but should NOT use hard coded model knowledge.
|
||||||
@ -448,6 +448,113 @@ describe('Permissions', function () {
|
|||||||
.catch(done);
|
.catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('App-based permissions (requires user as well)', function () {
|
||||||
|
// @TODO: revisit this - do we really need to have USER permissions AND app permissions?
|
||||||
|
it('No permissions: cannot edit tag with app only (no permissible function on model)', function (done) {
|
||||||
|
var appProviderStub = sinon.stub(providers, 'app').callsFake(function () {
|
||||||
|
// Fake the response from providers.app, which contains an empty array for this case
|
||||||
|
return Promise.resolve([]);
|
||||||
|
});
|
||||||
|
|
||||||
|
permissions
|
||||||
|
.canThis({app: {}}) // app context
|
||||||
|
.edit
|
||||||
|
.tag({id: 1}) // tag id in model syntax
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('was able to edit tag without permission'));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
appProviderStub.callCount.should.eql(1);
|
||||||
|
err.errorType.should.eql('NoPermissionError');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('No permissions: cannot edit tag (no permissible function on model)', function (done) {
|
||||||
|
var appProviderStub = sinon.stub(providers, 'app').callsFake(function () {
|
||||||
|
// Fake the response from providers.app, which contains an empty array for this case
|
||||||
|
return Promise.resolve([]);
|
||||||
|
}),
|
||||||
|
userProviderStub = sinon.stub(providers, 'user').callsFake(function () {
|
||||||
|
// Fake the response from providers.user, which contains permissions and roles
|
||||||
|
return Promise.resolve({
|
||||||
|
permissions: [],
|
||||||
|
roles: undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
permissions
|
||||||
|
.canThis({app: {}, user: {}}) // app context
|
||||||
|
.edit
|
||||||
|
.tag({id: 1}) // tag id in model syntax
|
||||||
|
.then(function () {
|
||||||
|
done(new Error('was able to edit tag without permission'));
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
appProviderStub.callCount.should.eql(1);
|
||||||
|
userProviderStub.callCount.should.eql(1);
|
||||||
|
err.errorType.should.eql('NoPermissionError');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('With permissions: can edit specific tag (no permissible function on model)', function (done) {
|
||||||
|
var appProviderStub = sinon.stub(providers, 'app').callsFake(function () {
|
||||||
|
// Fake the response from providers.app, which contains permissions only
|
||||||
|
return Promise.resolve({
|
||||||
|
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
userProviderStub = sinon.stub(providers, 'user').callsFake(function () {
|
||||||
|
// Fake the response from providers.user, which contains permissions and roles
|
||||||
|
return Promise.resolve({
|
||||||
|
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
|
||||||
|
roles: undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
permissions
|
||||||
|
.canThis({app: {}, user: {}}) // app context
|
||||||
|
.edit
|
||||||
|
.tag({id: 1}) // tag id in model syntax
|
||||||
|
.then(function (res) {
|
||||||
|
appProviderStub.callCount.should.eql(1);
|
||||||
|
userProviderStub.callCount.should.eql(1);
|
||||||
|
should.not.exist(res);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('With permissions: can edit non-specific tag (no permissible function on model)', function (done) {
|
||||||
|
var appProviderStub = sinon.stub(providers, 'app').callsFake(function () {
|
||||||
|
// Fake the response from providers.app, which contains permissions only
|
||||||
|
return Promise.resolve({
|
||||||
|
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
userProviderStub = sinon.stub(providers, 'user').callsFake(function () {
|
||||||
|
// Fake the response from providers.user, which contains permissions and roles
|
||||||
|
return Promise.resolve({
|
||||||
|
permissions: models.Permissions.forge(testUtils.DataGenerator.Content.permissions).models,
|
||||||
|
roles: undefined
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
permissions
|
||||||
|
.canThis({app: {}, user: {}}) // app context
|
||||||
|
.edit
|
||||||
|
.tag() // tag id in model syntax
|
||||||
|
.then(function (res) {
|
||||||
|
appProviderStub.callCount.should.eql(1);
|
||||||
|
userProviderStub.callCount.should.eql(1);
|
||||||
|
should.not.exist(res);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('permissible (overridden)', function () {
|
describe('permissible (overridden)', function () {
|
||||||
@ -472,7 +579,7 @@ describe('Permissions', function () {
|
|||||||
})
|
})
|
||||||
.catch(function (err) {
|
.catch(function (err) {
|
||||||
permissibleStub.callCount.should.eql(1);
|
permissibleStub.callCount.should.eql(1);
|
||||||
permissibleStub.firstCall.args.should.have.lengthOf(7);
|
permissibleStub.firstCall.args.should.have.lengthOf(8);
|
||||||
|
|
||||||
permissibleStub.firstCall.args[0].should.eql(1);
|
permissibleStub.firstCall.args[0].should.eql(1);
|
||||||
permissibleStub.firstCall.args[1].should.eql('edit');
|
permissibleStub.firstCall.args[1].should.eql('edit');
|
||||||
@ -481,6 +588,7 @@ describe('Permissions', function () {
|
|||||||
permissibleStub.firstCall.args[4].should.be.an.Object();
|
permissibleStub.firstCall.args[4].should.be.an.Object();
|
||||||
permissibleStub.firstCall.args[5].should.be.true();
|
permissibleStub.firstCall.args[5].should.be.true();
|
||||||
permissibleStub.firstCall.args[6].should.be.true();
|
permissibleStub.firstCall.args[6].should.be.true();
|
||||||
|
permissibleStub.firstCall.args[7].should.be.true();
|
||||||
|
|
||||||
userProviderStub.callCount.should.eql(1);
|
userProviderStub.callCount.should.eql(1);
|
||||||
err.message.should.eql('Hello World!');
|
err.message.should.eql('Hello World!');
|
||||||
@ -506,7 +614,7 @@ describe('Permissions', function () {
|
|||||||
.post({id: 1}) // tag id in model syntax
|
.post({id: 1}) // tag id in model syntax
|
||||||
.then(function (res) {
|
.then(function (res) {
|
||||||
permissibleStub.callCount.should.eql(1);
|
permissibleStub.callCount.should.eql(1);
|
||||||
permissibleStub.firstCall.args.should.have.lengthOf(7);
|
permissibleStub.firstCall.args.should.have.lengthOf(8);
|
||||||
permissibleStub.firstCall.args[0].should.eql(1);
|
permissibleStub.firstCall.args[0].should.eql(1);
|
||||||
permissibleStub.firstCall.args[1].should.eql('edit');
|
permissibleStub.firstCall.args[1].should.eql('edit');
|
||||||
permissibleStub.firstCall.args[2].should.be.an.Object();
|
permissibleStub.firstCall.args[2].should.be.an.Object();
|
||||||
@ -514,6 +622,7 @@ describe('Permissions', function () {
|
|||||||
permissibleStub.firstCall.args[4].should.be.an.Object();
|
permissibleStub.firstCall.args[4].should.be.an.Object();
|
||||||
permissibleStub.firstCall.args[5].should.be.true();
|
permissibleStub.firstCall.args[5].should.be.true();
|
||||||
permissibleStub.firstCall.args[6].should.be.true();
|
permissibleStub.firstCall.args[6].should.be.true();
|
||||||
|
permissibleStub.firstCall.args[7].should.be.true();
|
||||||
|
|
||||||
userProviderStub.callCount.should.eql(1);
|
userProviderStub.callCount.should.eql(1);
|
||||||
should.not.exist(res);
|
should.not.exist(res);
|
||||||
|
@ -9,6 +9,7 @@ describe('Permissions', function () {
|
|||||||
external: false,
|
external: false,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: true,
|
public: true,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -17,6 +18,7 @@ describe('Permissions', function () {
|
|||||||
external: false,
|
external: false,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: true,
|
public: true,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -28,6 +30,7 @@ describe('Permissions', function () {
|
|||||||
external: false,
|
external: false,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: true,
|
public: true,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -36,6 +39,7 @@ describe('Permissions', function () {
|
|||||||
external: false,
|
external: false,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: true,
|
public: true,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -47,6 +51,7 @@ describe('Permissions', function () {
|
|||||||
external: false,
|
external: false,
|
||||||
user: 1,
|
user: 1,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: false,
|
public: false,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -64,6 +69,7 @@ describe('Permissions', function () {
|
|||||||
id: 1,
|
id: 1,
|
||||||
type: 'content'
|
type: 'content'
|
||||||
},
|
},
|
||||||
|
app: null,
|
||||||
public: true,
|
public: true,
|
||||||
integration: {id: 2}
|
integration: {id: 2}
|
||||||
});
|
});
|
||||||
@ -81,17 +87,31 @@ describe('Permissions', function () {
|
|||||||
id: 1,
|
id: 1,
|
||||||
type: 'admin'
|
type: 'admin'
|
||||||
},
|
},
|
||||||
|
app: null,
|
||||||
public: false,
|
public: false,
|
||||||
integration: {id: 3}
|
integration: {id: 3}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return app if app populated', function () {
|
||||||
|
parseContext({app: 5}).should.eql({
|
||||||
|
internal: false,
|
||||||
|
external: false,
|
||||||
|
user: null,
|
||||||
|
api_key: null,
|
||||||
|
app: 5,
|
||||||
|
public: false,
|
||||||
|
integration: null
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return internal if internal provided', function () {
|
it('should return internal if internal provided', function () {
|
||||||
parseContext({internal: true}).should.eql({
|
parseContext({internal: true}).should.eql({
|
||||||
internal: true,
|
internal: true,
|
||||||
external: false,
|
external: false,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: false,
|
public: false,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -101,6 +121,7 @@ describe('Permissions', function () {
|
|||||||
external: false,
|
external: false,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: false,
|
public: false,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -112,6 +133,7 @@ describe('Permissions', function () {
|
|||||||
external: true,
|
external: true,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: false,
|
public: false,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
@ -121,6 +143,7 @@ describe('Permissions', function () {
|
|||||||
external: true,
|
external: true,
|
||||||
user: null,
|
user: null,
|
||||||
api_key: null,
|
api_key: null,
|
||||||
|
app: null,
|
||||||
public: false,
|
public: false,
|
||||||
integration: null
|
integration: null
|
||||||
});
|
});
|
||||||
|
@ -212,4 +212,60 @@ describe('Permission Providers', function () {
|
|||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('App', function () {
|
||||||
|
// @TODO make this consistent or sane or something!
|
||||||
|
// Why is this an empty array, when the success is an object?
|
||||||
|
// Also why is this an empty array when for users we error?!
|
||||||
|
it('returns empty array if app cannot be found!', function (done) {
|
||||||
|
var findAppSpy = sinon.stub(models.App, 'findOne').callsFake(function () {
|
||||||
|
return Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
providers.app('test')
|
||||||
|
.then(function (res) {
|
||||||
|
findAppSpy.callCount.should.eql(1);
|
||||||
|
res.should.be.an.Array().with.lengthOf(0);
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can load user with role, and permissions', function (done) {
|
||||||
|
// This test requires quite a lot of unique setup work
|
||||||
|
var findAppSpy = sinon.stub(models.App, 'findOne').callsFake(function () {
|
||||||
|
var fakeApp = models.App.forge(testUtils.DataGenerator.Content.apps[0]),
|
||||||
|
fakePermissions = models.Permissions.forge(testUtils.DataGenerator.Content.permissions);
|
||||||
|
|
||||||
|
// ## Fake the relations
|
||||||
|
fakeApp.relations = {
|
||||||
|
permissions: fakePermissions
|
||||||
|
};
|
||||||
|
fakeApp.include = ['permissions'];
|
||||||
|
|
||||||
|
return Promise.resolve(fakeApp);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get permissions for the app
|
||||||
|
providers.app('kudos')
|
||||||
|
.then(function (res) {
|
||||||
|
findAppSpy.callCount.should.eql(1);
|
||||||
|
|
||||||
|
res.should.be.an.Object().with.properties('permissions');
|
||||||
|
|
||||||
|
res.permissions.should.be.an.Array().with.lengthOf(10);
|
||||||
|
should.not.exist(res.roles);
|
||||||
|
|
||||||
|
// @TODO fix this!
|
||||||
|
// Permissions is an array of models
|
||||||
|
// Roles is a JSON array
|
||||||
|
res.permissions[0].should.be.an.Object().with.properties('attributes', 'id');
|
||||||
|
res.permissions[0].should.be.instanceOf(models.Base.Model);
|
||||||
|
|
||||||
|
done();
|
||||||
|
})
|
||||||
|
.catch(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -13,8 +13,9 @@ describe('Permissions', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return unchanged object for non-public context', function (done) {
|
it('should return unchanged object for non-public context', function (done) {
|
||||||
const internal = {context: 'internal'};
|
var internal = {context: 'internal'},
|
||||||
const user = {context: {user: 1}};
|
user = {context: {user: 1}},
|
||||||
|
app = {context: {app: 1}};
|
||||||
|
|
||||||
applyPublicRules('posts', 'browse', _.cloneDeep(internal)).then(function (result) {
|
applyPublicRules('posts', 'browse', _.cloneDeep(internal)).then(function (result) {
|
||||||
result.should.eql(internal);
|
result.should.eql(internal);
|
||||||
@ -23,6 +24,10 @@ describe('Permissions', function () {
|
|||||||
}).then(function (result) {
|
}).then(function (result) {
|
||||||
result.should.eql(user);
|
result.should.eql(user);
|
||||||
|
|
||||||
|
return applyPublicRules('posts', 'browse', _.cloneDeep(app));
|
||||||
|
}).then(function (result) {
|
||||||
|
result.should.eql(app);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
14
core/test/utils/fixtures/app/badinstall.js
Normal file
14
core/test/utils/fixtures/app/badinstall.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
var knex = require('knex');
|
||||||
|
|
||||||
|
return knex.dropTableIfExists('users');
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
5
core/test/utils/fixtures/app/badlib.js
Normal file
5
core/test/utils/fixtures/app/badlib.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
var knex = require('knex');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
knex: knex
|
||||||
|
};
|
14
core/test/utils/fixtures/app/badoutside.js
Normal file
14
core/test/utils/fixtures/app/badoutside.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
var lib = require('../example');
|
||||||
|
|
||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
return lib.answer;
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
14
core/test/utils/fixtures/app/badrequire.js
Normal file
14
core/test/utils/fixtures/app/badrequire.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
var lib = require('./badlib');
|
||||||
|
|
||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
return lib.knex.dropTableIfExists('users');
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
14
core/test/utils/fixtures/app/badtop.js
Normal file
14
core/test/utils/fixtures/app/badtop.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
var knex = require('knex');
|
||||||
|
|
||||||
|
function BadApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
BadApp.prototype.install = function () {
|
||||||
|
return knex.dropTableIfExists('users');
|
||||||
|
};
|
||||||
|
|
||||||
|
BadApp.prototype.activate = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = BadApp;
|
22
core/test/utils/fixtures/app/good.js
Normal file
22
core/test/utils/fixtures/app/good.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
var path = require('path'),
|
||||||
|
util = require('./goodlib.js'),
|
||||||
|
nested = require('./nested/goodnested');
|
||||||
|
|
||||||
|
function GoodApp(app) {
|
||||||
|
this.app = app;
|
||||||
|
}
|
||||||
|
|
||||||
|
GoodApp.prototype.install = function () {
|
||||||
|
// Goes through app to do data
|
||||||
|
this.app.something = 42;
|
||||||
|
this.app.util = util;
|
||||||
|
this.app.nested = nested;
|
||||||
|
this.app.path = path.join(__dirname, 'good.js');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
GoodApp.prototype.activate = function () {
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = GoodApp;
|
5
core/test/utils/fixtures/app/goodlib.js
Normal file
5
core/test/utils/fixtures/app/goodlib.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
module.exports = {
|
||||||
|
util: function () {
|
||||||
|
return 42;
|
||||||
|
}
|
||||||
|
};
|
5
core/test/utils/fixtures/app/nested/goodnested.js
Normal file
5
core/test/utils/fixtures/app/nested/goodnested.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
var lib = require('../goodlib.js');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
other: 42
|
||||||
|
};
|
@ -296,6 +296,60 @@ DataGenerator.Content = {
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
||||||
|
apps: [
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
name: 'Kudos',
|
||||||
|
slug: 'kudos',
|
||||||
|
version: '0.0.1',
|
||||||
|
status: 'installed'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
name: 'Importer',
|
||||||
|
slug: 'importer',
|
||||||
|
version: '0.1.0',
|
||||||
|
status: 'inactive'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
name: 'Hemingway',
|
||||||
|
slug: 'hemingway',
|
||||||
|
version: '1.0.0',
|
||||||
|
status: 'installed'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
app_fields: [
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
key: 'count',
|
||||||
|
value: '120',
|
||||||
|
type: 'number',
|
||||||
|
active: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
key: 'words',
|
||||||
|
value: '512',
|
||||||
|
type: 'number',
|
||||||
|
active: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
app_settings: [
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
key: 'color',
|
||||||
|
value: 'ghosty'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
key: 'setting',
|
||||||
|
value: 'value'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
subscribers: [
|
subscribers: [
|
||||||
{
|
{
|
||||||
id: ObjectId.generate(),
|
id: ObjectId.generate(),
|
||||||
@ -565,6 +619,40 @@ DataGenerator.forKnex = (function () {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createAppField(overrides) {
|
||||||
|
var newObj = _.cloneDeep(overrides);
|
||||||
|
|
||||||
|
return _.defaults(newObj, {
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
created_by: DataGenerator.Content.users[0].id,
|
||||||
|
created_at: new Date(),
|
||||||
|
active: true,
|
||||||
|
app_id: DataGenerator.Content.apps[0].id,
|
||||||
|
relatable_id: DataGenerator.Content.posts[0].id,
|
||||||
|
relatable_type: 'posts'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createAppSetting(overrides) {
|
||||||
|
var newObj = _.cloneDeep(overrides);
|
||||||
|
|
||||||
|
return _.defaults(newObj, {
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
app_id: DataGenerator.Content.apps[0].id,
|
||||||
|
created_by: DataGenerator.Content.users[0].id,
|
||||||
|
created_at: new Date()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function createSubscriber(overrides) {
|
||||||
|
const newObj = _.cloneDeep(overrides);
|
||||||
|
|
||||||
|
return _.defaults(newObj, {
|
||||||
|
id: ObjectId.generate(),
|
||||||
|
email: 'subscriber@ghost.org'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function createMember(overrides) {
|
function createMember(overrides) {
|
||||||
const newObj = _.cloneDeep(overrides);
|
const newObj = _.cloneDeep(overrides);
|
||||||
|
|
||||||
@ -811,6 +899,17 @@ DataGenerator.forKnex = (function () {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const apps = [
|
||||||
|
createBasic(DataGenerator.Content.apps[0]),
|
||||||
|
createBasic(DataGenerator.Content.apps[1]),
|
||||||
|
createBasic(DataGenerator.Content.apps[2])
|
||||||
|
];
|
||||||
|
|
||||||
|
const app_fields = [
|
||||||
|
createAppField(DataGenerator.Content.app_fields[0]),
|
||||||
|
createAppField(DataGenerator.Content.app_fields[1])
|
||||||
|
];
|
||||||
|
|
||||||
const invites = [
|
const invites = [
|
||||||
createInvite({email: 'test1@ghost.org', role_id: DataGenerator.Content.roles[0].id}),
|
createInvite({email: 'test1@ghost.org', role_id: DataGenerator.Content.roles[0].id}),
|
||||||
createInvite({email: 'test2@ghost.org', role_id: DataGenerator.Content.roles[2].id})
|
createInvite({email: 'test2@ghost.org', role_id: DataGenerator.Content.roles[2].id})
|
||||||
@ -850,8 +949,12 @@ DataGenerator.forKnex = (function () {
|
|||||||
createRole: createBasic,
|
createRole: createBasic,
|
||||||
createPermission: createBasic,
|
createPermission: createBasic,
|
||||||
createPostsTags: createPostsTags,
|
createPostsTags: createPostsTags,
|
||||||
|
createApp: createBasic,
|
||||||
|
createAppField: createAppField,
|
||||||
createSetting: createSetting,
|
createSetting: createSetting,
|
||||||
|
createAppSetting: createAppSetting,
|
||||||
createToken: createToken,
|
createToken: createToken,
|
||||||
|
createSubscriber: createSubscriber,
|
||||||
createMember: createMember,
|
createMember: createMember,
|
||||||
createInvite: createInvite,
|
createInvite: createInvite,
|
||||||
createWebhook: createWebhook,
|
createWebhook: createWebhook,
|
||||||
@ -862,6 +965,8 @@ DataGenerator.forKnex = (function () {
|
|||||||
tags: tags,
|
tags: tags,
|
||||||
posts_tags: posts_tags,
|
posts_tags: posts_tags,
|
||||||
posts_authors: posts_authors,
|
posts_authors: posts_authors,
|
||||||
|
apps: apps,
|
||||||
|
app_fields: app_fields,
|
||||||
roles: roles,
|
roles: roles,
|
||||||
users: users,
|
users: users,
|
||||||
roles_users: roles_users,
|
roles_users: roles_users,
|
||||||
|
@ -523,6 +523,21 @@ clearData = function clearData() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
toDoList = {
|
toDoList = {
|
||||||
|
app: function insertApp() {
|
||||||
|
return fixtures.insertOne('App', 'apps', 'createApp');
|
||||||
|
},
|
||||||
|
app_field: function insertAppField() {
|
||||||
|
// TODO: use the actual app ID to create the field
|
||||||
|
return fixtures.insertOne('App', 'apps', 'createApp').then(function () {
|
||||||
|
return fixtures.insertOne('AppField', 'app_fields', 'createAppField');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
app_setting: function insertAppSetting() {
|
||||||
|
// TODO: use the actual app ID to create the field
|
||||||
|
return fixtures.insertOne('App', 'apps', 'createApp').then(function () {
|
||||||
|
return fixtures.insertOne('AppSetting', 'app_settings', 'createAppSetting');
|
||||||
|
});
|
||||||
|
},
|
||||||
permission: function insertPermission() {
|
permission: function insertPermission() {
|
||||||
return fixtures.insertOne('Permission', 'permissions', 'createPermission');
|
return fixtures.insertOne('Permission', 'permissions', 'createPermission');
|
||||||
},
|
},
|
||||||
@ -535,6 +550,9 @@ toDoList = {
|
|||||||
tag: function insertTag() {
|
tag: function insertTag() {
|
||||||
return fixtures.insertOne('Tag', 'tags', 'createTag');
|
return fixtures.insertOne('Tag', 'tags', 'createTag');
|
||||||
},
|
},
|
||||||
|
subscriber: function insertSubscriber() {
|
||||||
|
return fixtures.insertOne('Subscriber', 'subscribers', 'createSubscriber');
|
||||||
|
},
|
||||||
member: function insertMember() {
|
member: function insertMember() {
|
||||||
return fixtures.insertOne('Member', 'members', 'createMember');
|
return fixtures.insertOne('Member', 'members', 'createMember');
|
||||||
},
|
},
|
||||||
@ -550,6 +568,9 @@ toDoList = {
|
|||||||
'tags:extra': function insertExtraTags() {
|
'tags:extra': function insertExtraTags() {
|
||||||
return fixtures.insertExtraTags();
|
return fixtures.insertExtraTags();
|
||||||
},
|
},
|
||||||
|
apps: function insertApps() {
|
||||||
|
return fixtures.insertApps();
|
||||||
|
},
|
||||||
settings: function populateSettings() {
|
settings: function populateSettings() {
|
||||||
settingsCache.shutdown();
|
settingsCache.shutdown();
|
||||||
return settingsService.init();
|
return settingsService.init();
|
||||||
@ -1013,6 +1034,10 @@ module.exports = {
|
|||||||
cacheStub.withArgs('amp').returns(true);
|
cacheStub.withArgs('amp').returns(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.apps) {
|
||||||
|
cacheStub.withArgs('active_apps').returns([]);
|
||||||
|
}
|
||||||
|
|
||||||
sandbox.stub(imageLib.imageSize, 'getImageSizeFromUrl').resolves();
|
sandbox.stub(imageLib.imageSize, 'getImageSizeFromUrl').resolves();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user