App Permissions from package.json

Progress on #2095

- Add new AppPermissions class with read() method
- has default permissions to read and browse posts
- uses default permissions if no package.json
- uses default permissions if no ghost object in package.json
- errors when reading malformed package.json
- uses ghost.permissions if found in package.json
This commit is contained in:
Jacob Gable 2014-02-11 19:04:13 -06:00 committed by Sebastian Gierlinger
parent 41cef386bc
commit 13d2d04c72
3 changed files with 240 additions and 23 deletions

View File

@ -6,6 +6,7 @@ var path = require('path'),
config = require('../config'),
AppSandbox = require('./sandbox'),
AppDependencies = require('./dependencies'),
AppPermissions = require('./permissions'),
loader;
// Get the full path to an app by name
@ -48,37 +49,53 @@ loader = {
// Load a app and return the instantiated app
installAppByName: function (name) {
// Install the apps dependendencies first
var deps = new AppDependencies(getAppAbsolutePath(name));
return deps.install().then(function () {
var app = getAppByName(name);
var appPath = getAppAbsolutePath(name),
deps = new AppDependencies(appPath);
// Check for an install() method on the app.
if (!_.isFunction(app.install)) {
return when.reject(new Error("Error loading app named " + name + "; no install() method defined."));
}
return deps.install()
.then(function () {
// Load app permissions
var perms = new AppPermissions(appPath);
// Run the app.install() method
// Wrapping the install() with a when because it's possible
// to not return a promise from it.
return when(app.install(appProxy)).then(function () {
return when.resolve(app);
return perms.read().otherwise(function (err) {
// Provide a helpful error about which app
return when.reject(new Error("Error loading app named " + name + "; problem reading permissions: " + err.message));
});
})
.then(function (appPerms) {
var app = getAppByName(name, appPerms);
// Check for an install() method on the app.
if (!_.isFunction(app.install)) {
return when.reject(new Error("Error loading app named " + name + "; no install() method defined."));
}
// Run the app.install() method
// Wrapping the install() with a when because it's possible
// to not return a promise from it.
return when(app.install(appProxy)).then(function () {
return when.resolve(app);
});
});
});
},
// Activate a app and return it
activateAppByName: function (name) {
var app = getAppByName(name);
var perms = new AppPermissions(getAppAbsolutePath(name));
// Check for an activate() method on the app.
if (!_.isFunction(app.activate)) {
return when.reject(new Error("Error loading app named " + name + "; no activate() method defined."));
}
return perms.read().then(function (appPerms) {
var app = getAppByName(name, appPerms);
// Wrapping the activate() with a when because it's possible
// to not return a promise from it.
return when(app.activate(appProxy)).then(function () {
return when.resolve(app);
// Check for an activate() method on the app.
if (!_.isFunction(app.activate)) {
return when.reject(new Error("Error loading app named " + name + "; no activate() method defined."));
}
// Wrapping the activate() with a when because it's possible
// to not return a promise from it.
return when(app.activate(appProxy)).then(function () {
return when.resolve(app);
});
});
}
};

View File

@ -0,0 +1,75 @@
var fs = require('fs'),
when = require('when'),
path = require('path'),
parsePackageJson = require('../require-tree').parsePackageJson;
function AppPermissions(appPath) {
this.appPath = appPath;
this.packagePath = path.join(this.appPath, 'package.json');
}
AppPermissions.prototype.read = function () {
var self = this,
def = when.defer();
this.checkPackageContentsExists()
.then(function (exists) {
if (!exists) {
// If no package.json, return default permissions
return def.resolve(AppPermissions.DefaultPermissions);
}
// Read and parse the package.json
self.getPackageContents()
.then(function (parsed) {
// If no permissions in the package.json then return the default permissions.
if (!(parsed.ghost && parsed.ghost.permissions)) {
return def.resolve(AppPermissions.DefaultPermissions);
}
// TODO: Validation on permissions object?
def.resolve(parsed.ghost.permissions);
})
.otherwise(def.reject);
})
.otherwise(def.reject);
return def.promise;
};
AppPermissions.prototype.checkPackageContentsExists = function () {
// Mostly just broken out for stubbing in unit tests
var def = when.defer();
fs.exists(this.packagePath, function (exists) {
def.resolve(exists);
});
return def.promise;
};
// Get the contents of the package.json in the appPath root
AppPermissions.prototype.getPackageContents = function () {
var messages = {
errors: [],
warns: []
};
return parsePackageJson(this.packagePath, messages)
.then(function (parsed) {
if (!parsed) {
return when.reject(new Error(messages.errors[0].message));
}
return parsed;
});
};
// Default permissions for an App.
AppPermissions.DefaultPermissions = {
posts: ['browse', 'read']
};
module.exports = AppPermissions;

View File

@ -5,13 +5,15 @@ var fs = require('fs'),
should = require('should'),
sinon = require('sinon'),
_ = require('lodash'),
when = require('when'),
helpers = require('../../server/helpers'),
filters = require('../../server/filters'),
// Stuff we are testing
appProxy = require('../../server/apps/proxy'),
AppSandbox = require('../../server/apps/sandbox'),
AppDependencies = require('../../server/apps/dependencies');
AppDependencies = require('../../server/apps/dependencies'),
AppPermissions = require('../../server/apps/permissions');
describe('Apps', function () {
@ -189,4 +191,127 @@ describe('Apps', function () {
});
});
});
describe('Permissions', function () {
var noGhostPackageJson = {
"name": "myapp",
"version": "0.0.1",
"description": "My example app",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Ghost",
"license": "MIT",
"dependencies": {
"ghost-app": "0.0.1"
}
},
validGhostPackageJson = {
"name": "myapp",
"version": "0.0.1",
"description": "My example app",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "Ghost",
"license": "MIT",
"dependencies": {
"ghost-app": "0.0.1"
},
"ghost": {
"permissions": {
"posts": ["browse", "read", "edit", "add", "delete"],
"users": ["browse", "read", "edit", "add", "delete"],
"settings": ["browse", "read", "edit", "add", "delete"]
}
}
};
it('has default permissions to read and browse posts', function () {
should.exist(AppPermissions.DefaultPermissions);
should.exist(AppPermissions.DefaultPermissions.posts);
AppPermissions.DefaultPermissions.posts.should.contain('browse');
AppPermissions.DefaultPermissions.posts.should.contain('read');
// Make it hurt to add more so additional checks are added here
_.keys(AppPermissions.DefaultPermissions).length.should.equal(1);
});
it('uses default permissions if no package.json', function (done) {
var perms = new AppPermissions("test");
// No package.json in this directory
sandbox.stub(perms, "checkPackageContentsExists").returns(when.resolve(false));
perms.read().then(function (readPerms) {
should.exist(readPerms);
readPerms.should.equal(AppPermissions.DefaultPermissions);
done();
}).otherwise(done);
});
it('uses default permissions if no ghost object in package.json', function (done) {
var perms = new AppPermissions("test"),
noGhostPackageJsonContents = JSON.stringify(noGhostPackageJson, null, 2);
// package.json IS in this directory
sandbox.stub(perms, "checkPackageContentsExists").returns(when.resolve(true));
// no ghost property on package
sandbox.stub(perms, "getPackageContents").returns(when.resolve(noGhostPackageJsonContents));
perms.read().then(function (readPerms) {
should.exist(readPerms);
readPerms.should.equal(AppPermissions.DefaultPermissions);
done();
}).otherwise(done);
});
it('rejects when reading malformed package.json', function (done) {
var perms = new AppPermissions("test");
// package.json IS in this directory
sandbox.stub(perms, "checkPackageContentsExists").returns(when.resolve(true));
// malformed JSON on package
sandbox.stub(perms, "getPackageContents").returns(when.reject(new Error('package.json file is malformed')));
perms.read().then(function (readPerms) {
done(new Error('should not resolve'));
}).otherwise(function () {
done();
});
});
it('reads from package.json in root of app directory', function (done) {
var perms = new AppPermissions("test"),
validGhostPackageJsonContents = validGhostPackageJson;
// package.json IS in this directory
sandbox.stub(perms, "checkPackageContentsExists").returns(when.resolve(true));
// valid ghost property on package
sandbox.stub(perms, "getPackageContents").returns(when.resolve(validGhostPackageJsonContents));
perms.read().then(function (readPerms) {
should.exist(readPerms);
readPerms.should.not.equal(AppPermissions.DefaultPermissions);
should.exist(readPerms.posts);
readPerms.posts.length.should.equal(5);
should.exist(readPerms.users);
readPerms.users.length.should.equal(5);
should.exist(readPerms.settings);
readPerms.settings.length.should.equal(5);
_.keys(readPerms).length.should.equal(3);
done();
}).otherwise(done);
});
});
});