mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
commit
afbcecc3f6
22 changed files with 910 additions and 293 deletions
|
@ -212,7 +212,7 @@ users = {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}).catch(function handleError(error) {
|
}).catch(function handleError(error) {
|
||||||
return errors.handleAPIError(error, 'You do not have permission to edit this user');
|
return errors.formatAndRejectAPIError(error, 'You do not have permission to edit this user');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,7 +280,7 @@ users = {
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}).catch(function handleError(error) {
|
}).catch(function handleError(error) {
|
||||||
return errors.handleAPIError(error, 'You do not have permission to add this user');
|
return errors.formatAndRejectAPIError(error, 'You do not have permission to add this user');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -375,7 +375,7 @@ users = {
|
||||||
options.status = 'all';
|
options.status = 'all';
|
||||||
return options;
|
return options;
|
||||||
}).catch(function handleError(error) {
|
}).catch(function handleError(error) {
|
||||||
return errors.handleAPIError(error, 'You do not have permission to destroy this user.');
|
return errors.formatAndRejectAPIError(error, 'You do not have permission to destroy this user.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -407,7 +407,7 @@ users = {
|
||||||
return Promise.reject(new errors.InternalServerError(error));
|
return Promise.reject(new errors.InternalServerError(error));
|
||||||
});
|
});
|
||||||
}, function (error) {
|
}, function (error) {
|
||||||
return errors.handleAPIError(error);
|
return errors.formatAndRejectAPIError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,7 +442,7 @@ users = {
|
||||||
return canThis(options.context).edit.user(options.data.password[0].user_id).then(function permissionGranted() {
|
return canThis(options.context).edit.user(options.data.password[0].user_id).then(function permissionGranted() {
|
||||||
return options;
|
return options;
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
return errors.handleAPIError(error, 'You do not have permission to change the password for this user');
|
return errors.formatAndRejectAPIError(error, 'You do not have permission to change the password for this user');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -494,7 +494,7 @@ users = {
|
||||||
}).then(function () {
|
}).then(function () {
|
||||||
return options;
|
return options;
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
return errors.handleAPIError(error);
|
return errors.formatAndRejectAPIError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -520,7 +520,7 @@ users = {
|
||||||
return pipeline(tasks, object, options).then(function formatResult(result) {
|
return pipeline(tasks, object, options).then(function formatResult(result) {
|
||||||
return Promise.resolve({users: result});
|
return Promise.resolve({users: result});
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
return errors.handleAPIError(error);
|
return errors.formatAndRejectAPIError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -182,7 +182,7 @@ utils = {
|
||||||
return permsPromise.then(function permissionGranted() {
|
return permsPromise.then(function permissionGranted() {
|
||||||
return options;
|
return options;
|
||||||
}).catch(function handleError(error) {
|
}).catch(function handleError(error) {
|
||||||
return errors.handleAPIError(error);
|
return errors.formatAndRejectAPIError(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
@ -213,7 +213,7 @@ utils = {
|
||||||
// forward error to next catch()
|
// forward error to next catch()
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}).catch(function handleError(error) {
|
}).catch(function handleError(error) {
|
||||||
return errors.handleAPIError(error);
|
return errors.formatAndRejectAPIError(error);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -207,7 +207,7 @@ errors = {
|
||||||
return {errors: errors, statusCode: statusCode};
|
return {errors: errors, statusCode: statusCode};
|
||||||
},
|
},
|
||||||
|
|
||||||
handleAPIError: function (error, permsMessage) {
|
formatAndRejectAPIError: function (error, permsMessage) {
|
||||||
if (!error) {
|
if (!error) {
|
||||||
return this.rejectError(
|
return this.rejectError(
|
||||||
new this.NoPermissionError(permsMessage || 'You do not have permission to perform this action')
|
new this.NoPermissionError(permsMessage || 'You do not have permission to perform this action')
|
||||||
|
@ -234,6 +234,14 @@ errors = {
|
||||||
return this.rejectError(new this.InternalServerError(error));
|
return this.rejectError(new this.InternalServerError(error));
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleAPIError: function errorHandler(err, req, res, next) {
|
||||||
|
/*jshint unused:false */
|
||||||
|
var httpErrors = this.formatHttpErrors(err);
|
||||||
|
this.logError(err);
|
||||||
|
// Send a properly formatted HTTP response containing the errors
|
||||||
|
res.status(httpErrors.statusCode).json({errors: httpErrors.errors});
|
||||||
|
},
|
||||||
|
|
||||||
renderErrorPage: function (code, err, req, res, next) {
|
renderErrorPage: function (code, err, req, res, next) {
|
||||||
/*jshint unused:false*/
|
/*jshint unused:false*/
|
||||||
var self = this,
|
var self = this,
|
||||||
|
@ -374,6 +382,7 @@ _.each([
|
||||||
'logErrorAndExit',
|
'logErrorAndExit',
|
||||||
'logErrorWithRedirect',
|
'logErrorWithRedirect',
|
||||||
'handleAPIError',
|
'handleAPIError',
|
||||||
|
'formatAndRejectAPIError',
|
||||||
'formatHttpErrors',
|
'formatHttpErrors',
|
||||||
'renderErrorPage',
|
'renderErrorPage',
|
||||||
'error404',
|
'error404',
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
var errors = require('../errors');
|
|
||||||
|
|
||||||
module.exports.errorHandler = function errorHandler(err, req, res, next) {
|
|
||||||
/*jshint unused:false */
|
|
||||||
var httpErrors = errors.formatHttpErrors(err);
|
|
||||||
errors.logError(err);
|
|
||||||
// Send a properly formatted HTTP response containing the errors
|
|
||||||
res.status(httpErrors.statusCode).json({errors: httpErrors.errors});
|
|
||||||
};
|
|
|
@ -13,10 +13,10 @@ strategies = {
|
||||||
* Use of the client password strategy is implemented to support ember-simple-auth.
|
* Use of the client password strategy is implemented to support ember-simple-auth.
|
||||||
*/
|
*/
|
||||||
clientPasswordStrategy: function clientPasswordStrategy(clientId, clientSecret, done) {
|
clientPasswordStrategy: function clientPasswordStrategy(clientId, clientSecret, done) {
|
||||||
return models.Client.findOne({slug: clientId})
|
return models.Client.findOne({slug: clientId}, {withRelated: ['trustedDomains']})
|
||||||
.then(function then(model) {
|
.then(function then(model) {
|
||||||
if (model) {
|
if (model) {
|
||||||
var client = model.toJSON();
|
var client = model.toJSON({include: ['trustedDomains']});
|
||||||
if (client.secret === clientSecret) {
|
if (client.secret === clientSecret) {
|
||||||
return done(null, client);
|
return done(null, client);
|
||||||
}
|
}
|
||||||
|
|
137
core/server/middleware/auth.js
Normal file
137
core/server/middleware/auth.js
Normal file
|
@ -0,0 +1,137 @@
|
||||||
|
var _ = require('lodash'),
|
||||||
|
passport = require('passport'),
|
||||||
|
url = require('url'),
|
||||||
|
errors = require('../errors'),
|
||||||
|
config = require('../config'),
|
||||||
|
oauthServer,
|
||||||
|
|
||||||
|
auth;
|
||||||
|
|
||||||
|
function cacheOauthServer(server) {
|
||||||
|
oauthServer = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBearerAutorizationHeader(req) {
|
||||||
|
var parts,
|
||||||
|
scheme,
|
||||||
|
credentials;
|
||||||
|
|
||||||
|
if (req.headers && req.headers.authorization) {
|
||||||
|
parts = req.headers.authorization.split(' ');
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (parts.length === 2) {
|
||||||
|
scheme = parts[0];
|
||||||
|
credentials = parts[1];
|
||||||
|
if (/^Bearer$/i.test(scheme)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidOrigin(origin, client) {
|
||||||
|
if (origin && client && client.type === 'ua' && (
|
||||||
|
_.some(client.trustedDomains, {trusted_domain: origin})
|
||||||
|
|| origin === url.parse(config.url).hostname
|
||||||
|
|| origin === url.parse(config.urlSSL ? config.urlSSL : '').hostname
|
||||||
|
)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = {
|
||||||
|
|
||||||
|
// ### Authenticate Client Middleware
|
||||||
|
authenticateClient: function authenticateClient(req, res, next) {
|
||||||
|
// skip client authentication if bearer token is present
|
||||||
|
if (isBearerAutorizationHeader(req)) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query && req.query.client_id) {
|
||||||
|
req.body.client_id = req.query.client_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.query && req.query.client_secret) {
|
||||||
|
req.body.client_secret = req.query.client_secret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!req.body.client_id || !req.body.client_secret) {
|
||||||
|
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||||
|
}
|
||||||
|
|
||||||
|
return passport.authenticate(['oauth2-client-password'], {session: false, failWithError: false},
|
||||||
|
function authenticate(err, client) {
|
||||||
|
var origin = null;
|
||||||
|
if (err) {
|
||||||
|
return next(err); // will generate a 500 error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (req.headers && req.headers.origin) {
|
||||||
|
origin = url.parse(req.headers.origin).hostname;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!origin && client && client.type === 'ua') {
|
||||||
|
res.header('Access-Control-Allow-Origin', config.url);
|
||||||
|
req.client = client;
|
||||||
|
return next(null, client);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidOrigin(origin, client)) {
|
||||||
|
res.header('Access-Control-Allow-Origin', req.headers.origin);
|
||||||
|
req.client = client;
|
||||||
|
return next(null, client);
|
||||||
|
} else {
|
||||||
|
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)(req, res, next);
|
||||||
|
},
|
||||||
|
|
||||||
|
// ### Authenticate User Middleware
|
||||||
|
authenticateUser: function authenticateUser(req, res, next) {
|
||||||
|
return passport.authenticate('bearer', {session: false, failWithError: false},
|
||||||
|
function authenticate(err, user, info) {
|
||||||
|
if (err) {
|
||||||
|
return next(err); // will generate a 500 error
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
req.authInfo = info;
|
||||||
|
req.user = user;
|
||||||
|
return next(null, user, info);
|
||||||
|
} else if (isBearerAutorizationHeader(req)) {
|
||||||
|
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||||
|
} else if (req.client) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors.handleAPIError(new errors.UnauthorizedError('Access denied.'), req, res, next);
|
||||||
|
}
|
||||||
|
)(req, res, next);
|
||||||
|
},
|
||||||
|
|
||||||
|
// Workaround for missing permissions
|
||||||
|
// TODO: rework when https://github.com/TryGhost/Ghost/issues/3911 is done
|
||||||
|
requiresAuthorizedUser: function requiresAuthorizedUser(req, res, next) {
|
||||||
|
if (req.user) {
|
||||||
|
return next();
|
||||||
|
} else {
|
||||||
|
return errors.handleAPIError(new errors.NoPermissionError('Please Sign In'), req, res, next);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// ### Generate access token Middleware
|
||||||
|
// register the oauth2orize middleware for password and refresh token grants
|
||||||
|
generateAccessToken: function generateAccessToken(req, res, next) {
|
||||||
|
return oauthServer.token()(req, res, next);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = auth;
|
||||||
|
module.exports.cacheOauthServer = cacheOauthServer;
|
|
@ -1,48 +0,0 @@
|
||||||
var passport = require('passport'),
|
|
||||||
apiErrorHandlers = require('./api-error-handlers');
|
|
||||||
|
|
||||||
// ### Authenticate Middleware
|
|
||||||
// authentication has to be done for /ghost/* routes with
|
|
||||||
// exceptions for signin, signout, signup, forgotten, reset only
|
|
||||||
// api and frontend use different authentication mechanisms atm
|
|
||||||
function authenticate(req, res, next) {
|
|
||||||
var path,
|
|
||||||
subPath;
|
|
||||||
|
|
||||||
// SubPath is the url path starting after any default subdirectories
|
|
||||||
// it is stripped of anything after the two levels `/ghost/.*?/` as the reset link has an argument
|
|
||||||
path = req.path;
|
|
||||||
/*jslint regexp:true, unparam:true*/
|
|
||||||
subPath = path.replace(/^(\/.*?\/.*?\/)(.*)?/, function replace(match, a) {
|
|
||||||
return a;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (subPath.indexOf('/ghost/api/') === 0
|
|
||||||
&& (path.indexOf('/ghost/api/v0.1/authentication/') !== 0
|
|
||||||
|| (path.indexOf('/ghost/api/v0.1/authentication/setup/') === 0 && req.method === 'PUT'))) {
|
|
||||||
return passport.authenticate('bearer', {session: false, failWithError: true},
|
|
||||||
function authenticate(err, user, info) {
|
|
||||||
if (err) {
|
|
||||||
return next(err); // will generate a 500 error
|
|
||||||
}
|
|
||||||
// Generate a JSON response reflecting authentication status
|
|
||||||
if (!user) {
|
|
||||||
var error = {
|
|
||||||
code: 401,
|
|
||||||
errorType: 'NoPermissionError',
|
|
||||||
message: 'Please Sign In'
|
|
||||||
};
|
|
||||||
|
|
||||||
return apiErrorHandlers.errorHandler(error, req, res, next);
|
|
||||||
}
|
|
||||||
// TODO: figure out, why user & authInfo is lost
|
|
||||||
req.authInfo = info;
|
|
||||||
req.user = user;
|
|
||||||
return next(null, user, info);
|
|
||||||
}
|
|
||||||
)(req, res, next);
|
|
||||||
}
|
|
||||||
next();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = authenticate;
|
|
|
@ -1,25 +0,0 @@
|
||||||
var passport = require('passport'),
|
|
||||||
oauthServer,
|
|
||||||
|
|
||||||
clientAuth;
|
|
||||||
|
|
||||||
function cacheOauthServer(server) {
|
|
||||||
oauthServer = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
clientAuth = {
|
|
||||||
// ### Authenticate Client Middleware
|
|
||||||
// authenticate client that is asking for an access token
|
|
||||||
authenticateClient: function authenticateClient(req, res, next) {
|
|
||||||
return passport.authenticate(['oauth2-client-password'], {session: false})(req, res, next);
|
|
||||||
},
|
|
||||||
|
|
||||||
// ### Generate access token Middleware
|
|
||||||
// register the oauth2orize middleware for password and refresh token grants
|
|
||||||
generateAccessToken: function generateAccessToken(req, res, next) {
|
|
||||||
return oauthServer.token()(req, res, next);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = clientAuth;
|
|
||||||
module.exports.cacheOauthServer = cacheOauthServer;
|
|
|
@ -12,11 +12,9 @@ var bodyParser = require('body-parser'),
|
||||||
utils = require('../utils'),
|
utils = require('../utils'),
|
||||||
sitemapHandler = require('../data/xml/sitemap/handler'),
|
sitemapHandler = require('../data/xml/sitemap/handler'),
|
||||||
|
|
||||||
apiErrorHandlers = require('./api-error-handlers'),
|
|
||||||
authenticate = require('./authenticate'),
|
|
||||||
authStrategies = require('./auth-strategies'),
|
authStrategies = require('./auth-strategies'),
|
||||||
busboy = require('./ghost-busboy'),
|
busboy = require('./ghost-busboy'),
|
||||||
clientAuth = require('./client-auth'),
|
auth = require('./auth'),
|
||||||
cacheControl = require('./cache-control'),
|
cacheControl = require('./cache-control'),
|
||||||
checkSSL = require('./check-ssl'),
|
checkSSL = require('./check-ssl'),
|
||||||
decideIsAdmin = require('./decide-is-admin'),
|
decideIsAdmin = require('./decide-is-admin'),
|
||||||
|
@ -41,10 +39,12 @@ middleware = {
|
||||||
spamPrevention: spamPrevention,
|
spamPrevention: spamPrevention,
|
||||||
privateBlogging: privateBlogging,
|
privateBlogging: privateBlogging,
|
||||||
api: {
|
api: {
|
||||||
cacheOauthServer: clientAuth.cacheOauthServer,
|
cacheOauthServer: auth.cacheOauthServer,
|
||||||
authenticateClient: clientAuth.authenticateClient,
|
authenticateClient: auth.authenticateClient,
|
||||||
generateAccessToken: clientAuth.generateAccessToken,
|
authenticateUser: auth.authenticateUser,
|
||||||
errorHandler: apiErrorHandlers.errorHandler
|
requiresAuthorizedUser: auth.requiresAuthorizedUser,
|
||||||
|
generateAccessToken: auth.generateAccessToken,
|
||||||
|
errorHandler: errors.handleAPIError
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -135,9 +135,6 @@ setupMiddleware = function setupMiddleware(blogApp, adminApp) {
|
||||||
// API shouldn't be cached
|
// API shouldn't be cached
|
||||||
blogApp.use(routes.apiBaseUri, cacheControl('private'));
|
blogApp.use(routes.apiBaseUri, cacheControl('private'));
|
||||||
|
|
||||||
// enable authentication
|
|
||||||
blogApp.use(authenticate);
|
|
||||||
|
|
||||||
// local data
|
// local data
|
||||||
blogApp.use(themeHandler.ghostLocals);
|
blogApp.use(themeHandler.ghostLocals);
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,7 @@ oauth = {
|
||||||
// `client`, which is exchanging the user's name and password from the
|
// `client`, which is exchanging the user's name and password from the
|
||||||
// authorization request for verification. If these values are validated, the
|
// authorization request for verification. If these values are validated, the
|
||||||
// application issues an access token on behalf of the user who authorized the code.
|
// application issues an access token on behalf of the user who authorized the code.
|
||||||
oauthServer.exchange(oauth2orize.exchange.password(function exchange(client, username, password, scope, done) {
|
oauthServer.exchange(oauth2orize.exchange.password({userProperty: 'client'}, function exchange(client, username, password, scope, done) {
|
||||||
// Validate the client
|
// Validate the client
|
||||||
models.Client.forge({slug: client.slug})
|
models.Client.forge({slug: client.slug})
|
||||||
.fetch()
|
.fetch()
|
||||||
|
|
|
@ -8,6 +8,27 @@ Client = ghostBookshelf.Model.extend({
|
||||||
trustedDomains: function trustedDomains() {
|
trustedDomains: function trustedDomains() {
|
||||||
return this.hasMany('ClientTrustedDomain', 'client_id');
|
return this.hasMany('ClientTrustedDomain', 'client_id');
|
||||||
}
|
}
|
||||||
|
}, {
|
||||||
|
/**
|
||||||
|
* 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(),
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Clients = ghostBookshelf.Collection.extend({
|
Clients = ghostBookshelf.Collection.extend({
|
||||||
|
|
|
@ -456,17 +456,17 @@ User = ghostBookshelf.Model.extend({
|
||||||
|
|
||||||
if (action === 'edit') {
|
if (action === 'edit') {
|
||||||
// Owner can only be editted by owner
|
// Owner can only be editted by owner
|
||||||
if (userModel.hasRole('Owner')) {
|
if (loadedPermissions.user && userModel.hasRole('Owner')) {
|
||||||
hasUserPermission = _.any(loadedPermissions.user.roles, {name: 'Owner'});
|
hasUserPermission = _.any(loadedPermissions.user.roles, {name: 'Owner'});
|
||||||
}
|
}
|
||||||
// Users with the role 'Editor' and 'Author' have complex permissions when the action === 'edit'
|
// Users with the role 'Editor' and 'Author' have complex permissions when the action === 'edit'
|
||||||
// We now have all the info we need to construct the permissions
|
// We now have all the info we need to construct the permissions
|
||||||
if (_.any(loadedPermissions.user.roles, {name: 'Author'})) {
|
if (loadedPermissions.user && _.any(loadedPermissions.user.roles, {name: 'Author'})) {
|
||||||
// If this is the same user that requests the operation allow it.
|
// If this is the same user that requests the operation allow it.
|
||||||
hasUserPermission = hasUserPermission || context.user === userModel.get('id');
|
hasUserPermission = hasUserPermission || context.user === userModel.get('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.any(loadedPermissions.user.roles, {name: 'Editor'})) {
|
if (loadedPermissions.user && _.any(loadedPermissions.user.roles, {name: 'Editor'})) {
|
||||||
// If this is the same user that requests the operation allow it.
|
// If this is the same user that requests the operation allow it.
|
||||||
hasUserPermission = context.user === userModel.get('id');
|
hasUserPermission = context.user === userModel.get('id');
|
||||||
|
|
||||||
|
@ -477,12 +477,12 @@ User = ghostBookshelf.Model.extend({
|
||||||
|
|
||||||
if (action === 'destroy') {
|
if (action === 'destroy') {
|
||||||
// Owner cannot be deleted EVER
|
// Owner cannot be deleted EVER
|
||||||
if (userModel.hasRole('Owner')) {
|
if (loadedPermissions.user && userModel.hasRole('Owner')) {
|
||||||
return Promise.reject(new errors.NoPermissionError('You do not have permission to perform this action'));
|
return Promise.reject(new errors.NoPermissionError('You do not have permission to perform this action'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Users with the role 'Editor' have complex permissions when the action === 'destroy'
|
// Users with the role 'Editor' have complex permissions when the action === 'destroy'
|
||||||
if (_.any(loadedPermissions.user.roles, {name: 'Editor'})) {
|
if (loadedPermissions.user && _.any(loadedPermissions.user.roles, {name: 'Editor'})) {
|
||||||
// If this is the same user that requests the operation allow it.
|
// If this is the same user that requests the operation allow it.
|
||||||
hasUserPermission = context.user === userModel.get('id');
|
hasUserPermission = context.user === userModel.get('id');
|
||||||
|
|
||||||
|
|
|
@ -169,7 +169,6 @@ CanThisResult.prototype.buildObjectTypeHandlers = function (objTypes, actType, c
|
||||||
// TODO: String vs Int comparison possibility here?
|
// TODO: String vs Int comparison possibility here?
|
||||||
return modelId === permObjId;
|
return modelId === permObjId;
|
||||||
};
|
};
|
||||||
// Check user permissions for matching action, object and id.
|
|
||||||
|
|
||||||
if (loadedPermissions.user && _.any(loadedPermissions.user.roles, {name: 'Owner'})) {
|
if (loadedPermissions.user && _.any(loadedPermissions.user.roles, {name: 'Owner'})) {
|
||||||
hasUserPermission = true;
|
hasUserPermission = true;
|
||||||
|
|
|
@ -4,72 +4,86 @@ var express = require('express'),
|
||||||
apiRoutes;
|
apiRoutes;
|
||||||
|
|
||||||
apiRoutes = function apiRoutes(middleware) {
|
apiRoutes = function apiRoutes(middleware) {
|
||||||
var router = express.Router();
|
var router = express.Router(),
|
||||||
|
// Authentication for public endpoints
|
||||||
|
authenticatePublic = [
|
||||||
|
middleware.api.authenticateClient,
|
||||||
|
middleware.api.authenticateUser
|
||||||
|
],
|
||||||
|
// Require user for private endpoints
|
||||||
|
authenticatePrivate = [
|
||||||
|
middleware.api.authenticateClient,
|
||||||
|
middleware.api.authenticateUser,
|
||||||
|
middleware.api.requiresAuthorizedUser
|
||||||
|
];
|
||||||
|
|
||||||
// alias delete with del
|
// alias delete with del
|
||||||
router.del = router.delete;
|
router.del = router.delete;
|
||||||
|
|
||||||
// ## Configuration
|
// ## Configuration
|
||||||
router.get('/configuration', api.http(api.configuration.browse));
|
router.get('/configuration', authenticatePrivate, api.http(api.configuration.browse));
|
||||||
router.get('/configuration/:key', api.http(api.configuration.read));
|
router.get('/configuration/:key', authenticatePrivate, api.http(api.configuration.read));
|
||||||
|
|
||||||
// ## Posts
|
// ## Posts
|
||||||
router.get('/posts', api.http(api.posts.browse));
|
router.get('/posts', authenticatePublic, api.http(api.posts.browse));
|
||||||
router.post('/posts', api.http(api.posts.add));
|
|
||||||
router.get('/posts/:id', api.http(api.posts.read));
|
router.post('/posts', authenticatePrivate, api.http(api.posts.add));
|
||||||
router.get('/posts/slug/:slug', api.http(api.posts.read));
|
router.get('/posts/:id', authenticatePublic, api.http(api.posts.read));
|
||||||
router.put('/posts/:id', api.http(api.posts.edit));
|
router.get('/posts/slug/:slug', authenticatePublic, api.http(api.posts.read));
|
||||||
router.del('/posts/:id', api.http(api.posts.destroy));
|
router.put('/posts/:id', authenticatePrivate, api.http(api.posts.edit));
|
||||||
|
router.del('/posts/:id', authenticatePrivate, api.http(api.posts.destroy));
|
||||||
|
|
||||||
// ## Settings
|
// ## Settings
|
||||||
router.get('/settings', api.http(api.settings.browse));
|
router.get('/settings', authenticatePrivate, api.http(api.settings.browse));
|
||||||
router.get('/settings/:key', api.http(api.settings.read));
|
router.get('/settings/:key', authenticatePrivate, api.http(api.settings.read));
|
||||||
router.put('/settings', api.http(api.settings.edit));
|
router.put('/settings', authenticatePrivate, api.http(api.settings.edit));
|
||||||
|
|
||||||
// ## Users
|
// ## Users
|
||||||
router.get('/users', api.http(api.users.browse));
|
router.get('/users', authenticatePublic, api.http(api.users.browse));
|
||||||
router.get('/users/:id', api.http(api.users.read));
|
|
||||||
router.get('/users/slug/:slug', api.http(api.users.read));
|
router.get('/users/:id', authenticatePublic, api.http(api.users.read));
|
||||||
router.get('/users/email/:email', api.http(api.users.read));
|
router.get('/users/slug/:slug', authenticatePublic, api.http(api.users.read));
|
||||||
router.put('/users/password', api.http(api.users.changePassword));
|
router.get('/users/email/:email', authenticatePublic, api.http(api.users.read));
|
||||||
router.put('/users/owner', api.http(api.users.transferOwnership));
|
router.put('/users/password', authenticatePrivate, api.http(api.users.changePassword));
|
||||||
router.put('/users/:id', api.http(api.users.edit));
|
router.put('/users/owner', authenticatePrivate, api.http(api.users.transferOwnership));
|
||||||
router.post('/users', api.http(api.users.add));
|
router.put('/users/:id', authenticatePrivate, api.http(api.users.edit));
|
||||||
router.del('/users/:id', api.http(api.users.destroy));
|
router.post('/users', authenticatePrivate, api.http(api.users.add));
|
||||||
|
router.del('/users/:id', authenticatePrivate, api.http(api.users.destroy));
|
||||||
|
|
||||||
// ## Tags
|
// ## Tags
|
||||||
router.get('/tags', api.http(api.tags.browse));
|
router.get('/tags', authenticatePublic, api.http(api.tags.browse));
|
||||||
router.get('/tags/:id', api.http(api.tags.read));
|
router.get('/tags/:id', authenticatePublic, api.http(api.tags.read));
|
||||||
router.get('/tags/slug/:slug', api.http(api.tags.read));
|
router.get('/tags/slug/:slug', authenticatePublic, api.http(api.tags.read));
|
||||||
router.post('/tags', api.http(api.tags.add));
|
router.post('/tags', authenticatePrivate, api.http(api.tags.add));
|
||||||
router.put('/tags/:id', api.http(api.tags.edit));
|
router.put('/tags/:id', authenticatePrivate, api.http(api.tags.edit));
|
||||||
router.del('/tags/:id', api.http(api.tags.destroy));
|
router.del('/tags/:id', authenticatePrivate, api.http(api.tags.destroy));
|
||||||
|
|
||||||
// ## Roles
|
// ## Roles
|
||||||
router.get('/roles/', api.http(api.roles.browse));
|
router.get('/roles/', authenticatePrivate, api.http(api.roles.browse));
|
||||||
|
|
||||||
// ## Clients
|
// ## Clients
|
||||||
router.get('/clients/slug/:slug', api.http(api.clients.read));
|
router.get('/clients/slug/:slug', api.http(api.clients.read));
|
||||||
|
|
||||||
// ## Slugs
|
// ## Slugs
|
||||||
router.get('/slugs/:type/:name', api.http(api.slugs.generate));
|
router.get('/slugs/:type/:name', authenticatePrivate, api.http(api.slugs.generate));
|
||||||
|
|
||||||
// ## Themes
|
// ## Themes
|
||||||
router.get('/themes', api.http(api.themes.browse));
|
router.get('/themes', authenticatePrivate, api.http(api.themes.browse));
|
||||||
router.put('/themes/:name', api.http(api.themes.edit));
|
router.put('/themes/:name', authenticatePrivate, api.http(api.themes.edit));
|
||||||
|
|
||||||
// ## Notifications
|
// ## Notifications
|
||||||
router.get('/notifications', api.http(api.notifications.browse));
|
router.get('/notifications', authenticatePrivate, api.http(api.notifications.browse));
|
||||||
router.post('/notifications', api.http(api.notifications.add));
|
router.post('/notifications', authenticatePrivate, api.http(api.notifications.add));
|
||||||
router.del('/notifications/:id', api.http(api.notifications.destroy));
|
router.del('/notifications/:id', authenticatePrivate, api.http(api.notifications.destroy));
|
||||||
|
|
||||||
// ## DB
|
// ## DB
|
||||||
router.get('/db', api.http(api.db.exportContent));
|
router.get('/db', authenticatePrivate, api.http(api.db.exportContent));
|
||||||
router.post('/db', middleware.busboy, api.http(api.db.importContent));
|
router.post('/db', authenticatePrivate, middleware.busboy, api.http(api.db.importContent));
|
||||||
router.del('/db', api.http(api.db.deleteAllContent));
|
router.del('/db', authenticatePrivate, api.http(api.db.deleteAllContent));
|
||||||
|
|
||||||
// ## Mail
|
// ## Mail
|
||||||
router.post('/mail', api.http(api.mail.send));
|
router.post('/mail', authenticatePrivate, api.http(api.mail.send));
|
||||||
router.post('/mail/test', api.http(api.mail.sendTest));
|
router.post('/mail/test', authenticatePrivate, api.http(api.mail.sendTest));
|
||||||
|
|
||||||
// ## Authentication
|
// ## Authentication
|
||||||
router.post('/authentication/passwordreset',
|
router.post('/authentication/passwordreset',
|
||||||
|
@ -87,10 +101,10 @@ apiRoutes = function apiRoutes(middleware) {
|
||||||
middleware.api.authenticateClient,
|
middleware.api.authenticateClient,
|
||||||
middleware.api.generateAccessToken
|
middleware.api.generateAccessToken
|
||||||
);
|
);
|
||||||
router.post('/authentication/revoke', api.http(api.authentication.revoke));
|
router.post('/authentication/revoke', authenticatePrivate, api.http(api.authentication.revoke));
|
||||||
|
|
||||||
// ## Uploads
|
// ## Uploads
|
||||||
router.post('/uploads', middleware.busboy, api.http(api.uploads.add));
|
router.post('/uploads', authenticatePrivate, middleware.busboy, api.http(api.uploads.add));
|
||||||
|
|
||||||
// API Router middleware
|
// API Router middleware
|
||||||
router.use(middleware.api.errorHandler);
|
router.use(middleware.api.errorHandler);
|
||||||
|
|
|
@ -5,6 +5,7 @@ var supertest = require('supertest'),
|
||||||
testUtils = require('../../../utils'),
|
testUtils = require('../../../utils'),
|
||||||
user = testUtils.DataGenerator.forModel.users[0],
|
user = testUtils.DataGenerator.forModel.users[0],
|
||||||
ghost = require('../../../../../core'),
|
ghost = require('../../../../../core'),
|
||||||
|
config = require('../../../../../core/server/config'),
|
||||||
request;
|
request;
|
||||||
|
|
||||||
describe('Authentication API', function () {
|
describe('Authentication API', function () {
|
||||||
|
@ -31,8 +32,14 @@ describe('Authentication API', function () {
|
||||||
|
|
||||||
it('can authenticate', function (done) {
|
it('can authenticate', function (done) {
|
||||||
request.post(testUtils.API.getApiQuery('authentication/token'))
|
request.post(testUtils.API.getApiQuery('authentication/token'))
|
||||||
.send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'})
|
.set('Origin', config.url)
|
||||||
.expect('Content-Type', /json/)
|
.send({
|
||||||
|
grant_type: 'password',
|
||||||
|
username: user.email,
|
||||||
|
password: user.password,
|
||||||
|
client_id: 'ghost-admin',
|
||||||
|
client_secret: 'not_available'
|
||||||
|
}).expect('Content-Type', /json/)
|
||||||
// TODO: make it possible to override oauth2orize's header so that this is consistent
|
// TODO: make it possible to override oauth2orize's header so that this is consistent
|
||||||
.expect('Cache-Control', 'no-store')
|
.expect('Cache-Control', 'no-store')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -52,8 +59,14 @@ describe('Authentication API', function () {
|
||||||
|
|
||||||
it('can\'t authenticate unknown user', function (done) {
|
it('can\'t authenticate unknown user', function (done) {
|
||||||
request.post(testUtils.API.getApiQuery('authentication/token'))
|
request.post(testUtils.API.getApiQuery('authentication/token'))
|
||||||
.send({grant_type: 'password', username: 'invalid@email.com', password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'})
|
.set('Origin', config.url)
|
||||||
.expect('Content-Type', /json/)
|
.send({
|
||||||
|
grant_type: 'password',
|
||||||
|
username: 'invalid@email.com',
|
||||||
|
password: user.password,
|
||||||
|
client_id: 'ghost-admin',
|
||||||
|
client_secret: 'not_available'
|
||||||
|
}).expect('Content-Type', /json/)
|
||||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
.expect(404)
|
.expect(404)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
|
@ -69,8 +82,14 @@ describe('Authentication API', function () {
|
||||||
|
|
||||||
it('can\'t authenticate invalid password user', function (done) {
|
it('can\'t authenticate invalid password user', function (done) {
|
||||||
request.post(testUtils.API.getApiQuery('authentication/token'))
|
request.post(testUtils.API.getApiQuery('authentication/token'))
|
||||||
.send({grant_type: 'password', username: user.email, password: 'invalid', client_id: 'ghost-admin', client_secret: 'not_available'})
|
.set('Origin', config.url)
|
||||||
.expect('Content-Type', /json/)
|
.send({
|
||||||
|
grant_type: 'password',
|
||||||
|
username: user.email,
|
||||||
|
password: 'invalid',
|
||||||
|
client_id: 'ghost-admin',
|
||||||
|
client_secret: 'not_available'
|
||||||
|
}).expect('Content-Type', /json/)
|
||||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
.expect(401)
|
.expect(401)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
|
@ -86,8 +105,14 @@ describe('Authentication API', function () {
|
||||||
|
|
||||||
it('can request new access token', function (done) {
|
it('can request new access token', function (done) {
|
||||||
request.post(testUtils.API.getApiQuery('authentication/token'))
|
request.post(testUtils.API.getApiQuery('authentication/token'))
|
||||||
.send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'})
|
.set('Origin', config.url)
|
||||||
.expect('Content-Type', /json/)
|
.send({
|
||||||
|
grant_type: 'password',
|
||||||
|
username: user.email,
|
||||||
|
password: user.password,
|
||||||
|
client_id: 'ghost-admin',
|
||||||
|
client_secret: 'not_available'
|
||||||
|
}).expect('Content-Type', /json/)
|
||||||
// TODO: make it possible to override oauth2orize's header so that this is consistent
|
// TODO: make it possible to override oauth2orize's header so that this is consistent
|
||||||
.expect('Cache-Control', 'no-store')
|
.expect('Cache-Control', 'no-store')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
|
@ -97,10 +122,15 @@ describe('Authentication API', function () {
|
||||||
}
|
}
|
||||||
var refreshToken = res.body.refresh_token;
|
var refreshToken = res.body.refresh_token;
|
||||||
request.post(testUtils.API.getApiQuery('authentication/token'))
|
request.post(testUtils.API.getApiQuery('authentication/token'))
|
||||||
.send({grant_type: 'refresh_token', refresh_token: refreshToken, client_id: 'ghost-admin', client_secret: 'not_available'})
|
.set('Origin', config.url)
|
||||||
.expect('Content-Type', /json/)
|
.send({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: refreshToken,
|
||||||
|
client_id: 'ghost-admin',
|
||||||
|
client_secret: 'not_available'
|
||||||
|
}).expect('Content-Type', /json/)
|
||||||
// TODO: make it possible to override oauth2orize's header so that this is consistent
|
// TODO: make it possible to override oauth2orize's header so that this is consistent
|
||||||
.expect('Cache-Control', 'no-store')
|
.expect('Cache-Control', 'no-store')
|
||||||
.expect(200)
|
.expect(200)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
|
@ -116,8 +146,13 @@ describe('Authentication API', function () {
|
||||||
|
|
||||||
it('can\'t request new access token with invalid refresh token', function (done) {
|
it('can\'t request new access token with invalid refresh token', function (done) {
|
||||||
request.post(testUtils.API.getApiQuery('authentication/token'))
|
request.post(testUtils.API.getApiQuery('authentication/token'))
|
||||||
.send({grant_type: 'refresh_token', refresh_token: 'invalid', client_id: 'ghost-admin', client_secret: 'not_available'})
|
.set('Origin', config.url)
|
||||||
.expect('Content-Type', /json/)
|
.send({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
refresh_token: 'invalid',
|
||||||
|
client_id: 'ghost-admin',
|
||||||
|
client_secret: 'not_available'
|
||||||
|
}).expect('Content-Type', /json/)
|
||||||
.expect('Cache-Control', testUtils.cacheRules.private)
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
.expect(403)
|
.expect(403)
|
||||||
.end(function (err, res) {
|
.end(function (err, res) {
|
||||||
|
|
153
core/test/functional/routes/api/public_api_spec.js
Normal file
153
core/test/functional/routes/api/public_api_spec.js
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
/*global describe, it, before, after */
|
||||||
|
/*jshint expr:true*/
|
||||||
|
var testUtils = require('../../../utils'),
|
||||||
|
should = require('should'),
|
||||||
|
supertest = require('supertest'),
|
||||||
|
_ = require('lodash'),
|
||||||
|
|
||||||
|
ghost = require('../../../../../core'),
|
||||||
|
|
||||||
|
request;
|
||||||
|
|
||||||
|
describe('Public API', function () {
|
||||||
|
before(function (done) {
|
||||||
|
// starting ghost automatically populates the db
|
||||||
|
// TODO: prevent db init, and manage bringing up the DB with fixtures ourselves
|
||||||
|
ghost().then(function (ghostServer) {
|
||||||
|
request = supertest.agent(ghostServer.rootApp);
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function (done) {
|
||||||
|
testUtils.clearData().then(function () {
|
||||||
|
done();
|
||||||
|
}).catch(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('browse posts', function (done) {
|
||||||
|
request.get(testUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
|
||||||
|
.set('Origin', testUtils.API.getURL())
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(200)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
should.not.exist(res.headers['x-cache-invalidate']);
|
||||||
|
var jsonResponse = res.body;
|
||||||
|
jsonResponse.posts.should.exist;
|
||||||
|
testUtils.API.checkResponse(jsonResponse, 'posts');
|
||||||
|
jsonResponse.posts.should.have.length(1);
|
||||||
|
testUtils.API.checkResponse(jsonResponse.posts[0], 'post');
|
||||||
|
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||||
|
_.isBoolean(jsonResponse.posts[0].featured).should.eql(true);
|
||||||
|
_.isBoolean(jsonResponse.posts[0].page).should.eql(true);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('browse tags', function (done) {
|
||||||
|
request.get(testUtils.API.getApiQuery('tags/?client_id=ghost-admin&client_secret=not_available'))
|
||||||
|
.set('Origin', testUtils.API.getURL())
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(200)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
should.not.exist(res.headers['x-cache-invalidate']);
|
||||||
|
var jsonResponse = res.body;
|
||||||
|
jsonResponse.tags.should.exist;
|
||||||
|
testUtils.API.checkResponse(jsonResponse, 'tags');
|
||||||
|
jsonResponse.tags.should.have.length(1);
|
||||||
|
testUtils.API.checkResponse(jsonResponse.tags[0], 'tag');
|
||||||
|
testUtils.API.checkResponse(jsonResponse.meta.pagination, 'pagination');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('denies access with invalid client_secret', function (done) {
|
||||||
|
request.get(testUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=invalid_secret'))
|
||||||
|
.set('Origin', testUtils.API.getURL())
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(401)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
should.not.exist(res.headers['x-cache-invalidate']);
|
||||||
|
var jsonResponse = res.body;
|
||||||
|
jsonResponse.should.exist;
|
||||||
|
jsonResponse.errors.should.exist;
|
||||||
|
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('denies access with invalid client_id', function (done) {
|
||||||
|
request.get(testUtils.API.getApiQuery('posts/?client_id=invalid-id&client_secret=not_available'))
|
||||||
|
.set('Origin', testUtils.API.getURL())
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(401)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
should.not.exist(res.headers['x-cache-invalidate']);
|
||||||
|
var jsonResponse = res.body;
|
||||||
|
jsonResponse.should.exist;
|
||||||
|
jsonResponse.errors.should.exist;
|
||||||
|
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('denies access from invalid origin', function (done) {
|
||||||
|
request.get(testUtils.API.getApiQuery('posts/?client_id=ghost-admin&client_secret=not_available'))
|
||||||
|
.set('Origin', 'http://invalid-origin')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(401)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
should.not.exist(res.headers['x-cache-invalidate']);
|
||||||
|
var jsonResponse = res.body;
|
||||||
|
jsonResponse.should.exist;
|
||||||
|
jsonResponse.errors.should.exist;
|
||||||
|
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('denies access to settings endpoint', function (done) {
|
||||||
|
request.get(testUtils.API.getApiQuery('settings/?client_id=ghost-admin&client_secret=not_available'))
|
||||||
|
.set('Origin', testUtils.API.getURL())
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect('Cache-Control', testUtils.cacheRules.private)
|
||||||
|
.expect(403)
|
||||||
|
.end(function (err, res) {
|
||||||
|
if (err) {
|
||||||
|
return done(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
should.not.exist(res.headers['x-cache-invalidate']);
|
||||||
|
var jsonResponse = res.body;
|
||||||
|
jsonResponse.should.exist;
|
||||||
|
jsonResponse.errors.should.exist;
|
||||||
|
testUtils.API.checkResponseValue(jsonResponse.errors[0], ['message', 'errorType']);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -276,6 +276,42 @@ describe('Error handling', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('API Error Handlers', function () {
|
||||||
|
var sandbox, req, res, next;
|
||||||
|
|
||||||
|
beforeEach(function () {
|
||||||
|
sandbox = sinon.sandbox.create();
|
||||||
|
req = {};
|
||||||
|
res = {};
|
||||||
|
res.json = sandbox.spy();
|
||||||
|
res.status = sandbox.stub().returns(res);
|
||||||
|
next = sandbox.spy();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function () {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('handleAPIError: sends a JSON error response', function () {
|
||||||
|
errors.logError = sandbox.spy(errors, 'logError');
|
||||||
|
errors.formatHttpErrors = sandbox.spy(errors, 'formatHttpErrors');
|
||||||
|
|
||||||
|
var msg = 'Something got lost',
|
||||||
|
err = new errors.NotFoundError(msg);
|
||||||
|
|
||||||
|
errors.handleAPIError(err, req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.false;
|
||||||
|
errors.logError.calledOnce.should.be.true;
|
||||||
|
errors.formatHttpErrors.calledOnce.should.be.true;
|
||||||
|
|
||||||
|
res.status.calledWith(404).should.be.true;
|
||||||
|
res.json.calledOnce.should.be.true;
|
||||||
|
res.json.firstCall.args[0].errors[0].message.should.eql(msg);
|
||||||
|
res.json.firstCall.args[0].errors[0].errorType.should.eql('NotFoundError');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Rendering', function () {
|
describe('Rendering', function () {
|
||||||
var sandbox,
|
var sandbox,
|
||||||
originalConfig;
|
originalConfig;
|
||||||
|
|
|
@ -1,48 +0,0 @@
|
||||||
/*globals describe, beforeEach, afterEach, it*/
|
|
||||||
/*jshint expr:true*/
|
|
||||||
var should = require('should'),
|
|
||||||
sinon = require('sinon'),
|
|
||||||
|
|
||||||
middleware = require('../../../server/middleware').middleware,
|
|
||||||
errors = require('../../../server/errors');
|
|
||||||
|
|
||||||
// To stop jshint complaining
|
|
||||||
should.equal(true, true);
|
|
||||||
|
|
||||||
describe('Middleware: API Error Handlers', function () {
|
|
||||||
var sandbox, req, res, next;
|
|
||||||
|
|
||||||
beforeEach(function () {
|
|
||||||
sandbox = sinon.sandbox.create();
|
|
||||||
req = {};
|
|
||||||
res = {};
|
|
||||||
res.json = sandbox.spy();
|
|
||||||
res.status = sandbox.stub().returns(res);
|
|
||||||
next = sandbox.spy();
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(function () {
|
|
||||||
sandbox.restore();
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('errorHandler', function () {
|
|
||||||
it('sends a JSON error response', function () {
|
|
||||||
errors.logError = sandbox.spy(errors, 'logError');
|
|
||||||
errors.formatHttpErrors = sandbox.spy(errors, 'formatHttpErrors');
|
|
||||||
|
|
||||||
var msg = 'Something got lost',
|
|
||||||
err = new errors.NotFoundError(msg);
|
|
||||||
|
|
||||||
middleware.api.errorHandler(err, req, res, next);
|
|
||||||
|
|
||||||
next.called.should.be.false;
|
|
||||||
errors.logError.calledOnce.should.be.true;
|
|
||||||
errors.formatHttpErrors.calledOnce.should.be.true;
|
|
||||||
|
|
||||||
res.status.calledWith(404).should.be.true;
|
|
||||||
res.json.calledOnce.should.be.true;
|
|
||||||
res.json.firstCall.args[0].errors[0].message.should.eql(msg);
|
|
||||||
res.json.firstCall.args[0].errors[0].errorType.should.eql('NotFoundError');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,13 +1,24 @@
|
||||||
/*globals describe, it, beforeEach, afterEach */
|
/*globals describe, it, beforeEach, afterEach */
|
||||||
/*jshint expr:true*/
|
/*jshint expr:true*/
|
||||||
var sinon = require('sinon'),
|
var _ = require('lodash'),
|
||||||
should = require('should'),
|
sinon = require('sinon'),
|
||||||
passport = require('passport'),
|
should = require('should'),
|
||||||
authenticate = require('../../../server/middleware/authenticate'),
|
passport = require('passport'),
|
||||||
BearerStrategy = require('passport-http-bearer').Strategy,
|
rewire = require('rewire'),
|
||||||
user = {id: 1},
|
config = require('../../../server/config'),
|
||||||
info = {scope: '*'},
|
defaultConfig = rewire('../../../../config.example')[process.env.NODE_ENV],
|
||||||
token = 'test_token';
|
auth = rewire('../../../server/middleware/auth'),
|
||||||
|
BearerStrategy = require('passport-http-bearer').Strategy,
|
||||||
|
ClientPasswordStrategy = require('passport-oauth2-client-password').Strategy,
|
||||||
|
user = {id: 1},
|
||||||
|
info = {scope: '*'},
|
||||||
|
token = 'test_token',
|
||||||
|
testClient = 'test_client',
|
||||||
|
testSecret = 'not_available',
|
||||||
|
client = {
|
||||||
|
id: 2,
|
||||||
|
type: 'ua'
|
||||||
|
};
|
||||||
|
|
||||||
should.equal(true, true);
|
should.equal(true, true);
|
||||||
|
|
||||||
|
@ -31,7 +42,50 @@ function registerUnsuccessfulBearerStrategy() {
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('authenticate', function () {
|
function registerFaultyBearerStrategy() {
|
||||||
|
// register fake BearerStrategy which always authenticates
|
||||||
|
passport.use(new BearerStrategy(
|
||||||
|
function strategy(accessToken, done) {
|
||||||
|
accessToken.should.eql(token);
|
||||||
|
return done('error');
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerSuccessfulClientPasswordStrategy() {
|
||||||
|
// register fake BearerStrategy which always authenticates
|
||||||
|
passport.use(new ClientPasswordStrategy(
|
||||||
|
function strategy(clientId, clientSecret, done) {
|
||||||
|
clientId.should.eql(testClient);
|
||||||
|
clientSecret.should.eql('not_available');
|
||||||
|
return done(null, client);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerUnsuccessfulClientPasswordStrategy() {
|
||||||
|
// register fake BearerStrategy which always authenticates
|
||||||
|
passport.use(new ClientPasswordStrategy(
|
||||||
|
function strategy(clientId, clientSecret, done) {
|
||||||
|
clientId.should.eql(testClient);
|
||||||
|
clientSecret.should.eql('not_available');
|
||||||
|
return done(null, false);
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
function registerFaultyClientPasswordStrategy() {
|
||||||
|
// register fake BearerStrategy which always authenticates
|
||||||
|
passport.use(new ClientPasswordStrategy(
|
||||||
|
function strategy(clientId, clientSecret, done) {
|
||||||
|
clientId.should.eql(testClient);
|
||||||
|
clientSecret.should.eql('not_available');
|
||||||
|
return done('error');
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Auth', function () {
|
||||||
var res, req, next, sandbox;
|
var res, req, next, sandbox;
|
||||||
|
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
|
@ -45,82 +99,21 @@ describe('authenticate', function () {
|
||||||
sandbox.restore();
|
sandbox.restore();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip authentication if not hitting /ghost', function (done) {
|
it('should require authorized user (user exists)', function (done) {
|
||||||
req.path = '/tag/foo/';
|
req.user = {id: 1};
|
||||||
req.method = 'GET';
|
|
||||||
|
|
||||||
registerSuccessfulBearerStrategy();
|
|
||||||
authenticate(req, res, next);
|
|
||||||
|
|
||||||
|
auth.requiresAuthorizedUser(req, res, next);
|
||||||
next.called.should.be.true;
|
next.called.should.be.true;
|
||||||
next.calledWith().should.be.true;
|
next.calledWith().should.be.true;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should skip authentication if hitting /ghost/api/v0.1/authenticaton/', function (done) {
|
it('should require authorized user (user is missing)', function (done) {
|
||||||
req.path = '/ghost/api/v0.1/authentication/';
|
req.user = false;
|
||||||
req.method = 'GET';
|
|
||||||
|
|
||||||
registerSuccessfulBearerStrategy();
|
|
||||||
authenticate(req, res, next);
|
|
||||||
|
|
||||||
next.called.should.be.true;
|
|
||||||
next.calledWith().should.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should skip authentication if hitting GET /ghost/api/v0.1/authenticaton/setup/', function (done) {
|
|
||||||
req.path = '/ghost/api/v0.1/authentication/setup/';
|
|
||||||
req.method = 'GET';
|
|
||||||
|
|
||||||
registerSuccessfulBearerStrategy();
|
|
||||||
authenticate(req, res, next);
|
|
||||||
|
|
||||||
next.called.should.be.true;
|
|
||||||
next.calledWith().should.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should authentication if hitting PUT /ghost/api/v0.1/authenticaton/setup/', function (done) {
|
|
||||||
req.path = '/ghost/api/v0.1/authentication/setup/';
|
|
||||||
req.method = 'PUT';
|
|
||||||
req.headers = {};
|
|
||||||
req.headers.authorization = 'Bearer ' + token;
|
|
||||||
|
|
||||||
registerSuccessfulBearerStrategy();
|
|
||||||
authenticate(req, res, next);
|
|
||||||
|
|
||||||
next.called.should.be.true;
|
|
||||||
next.calledWith(null, user, info).should.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should authenticate if hitting /ghost/api/ endpoint', function (done) {
|
|
||||||
req.path = '/ghost/api/v0.1/test/';
|
|
||||||
req.method = 'PUT';
|
|
||||||
req.headers = {};
|
|
||||||
req.headers.authorization = 'Bearer ' + token;
|
|
||||||
|
|
||||||
registerSuccessfulBearerStrategy();
|
|
||||||
authenticate(req, res, next);
|
|
||||||
|
|
||||||
next.called.should.be.true;
|
|
||||||
next.calledWith(null, user, info).should.be.true;
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('shouldn\'t authenticate if hitting /ghost/ auth endpoint with invalid credentials', function (done) {
|
|
||||||
res.status = {};
|
res.status = {};
|
||||||
req.path = '/ghost/api/v0.1/test/';
|
|
||||||
req.method = 'PUT';
|
|
||||||
req.headers = {};
|
|
||||||
req.headers.authorization = 'Bearer ' + token;
|
|
||||||
|
|
||||||
registerUnsuccessfulBearerStrategy();
|
|
||||||
|
|
||||||
// stub res.status for error handling
|
|
||||||
sandbox.stub(res, 'status', function (statusCode) {
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
statusCode.should.eql(401);
|
statusCode.should.eql(403);
|
||||||
return {
|
return {
|
||||||
json: function (err) {
|
json: function (err) {
|
||||||
err.errors[0].errorType.should.eql('NoPermissionError');
|
err.errors[0].errorType.should.eql('NoPermissionError');
|
||||||
|
@ -128,8 +121,349 @@ describe('authenticate', function () {
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
authenticate(req, res, next);
|
auth.requiresAuthorizedUser(req, res, next);
|
||||||
next.called.should.be.false;
|
next.called.should.be.false;
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('User Authentication', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
var newConfig = _.extend({}, config, defaultConfig);
|
||||||
|
|
||||||
|
auth.__get__('config', newConfig);
|
||||||
|
config.set(newConfig);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate user', function (done) {
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.authorization = 'Bearer ' + token;
|
||||||
|
|
||||||
|
registerSuccessfulBearerStrategy();
|
||||||
|
auth.authenticateUser(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith(null, user, info).should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t pass with client, no bearer token', function (done) {
|
||||||
|
req.headers = {};
|
||||||
|
req.client = {id: 1};
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
auth.authenticateUser(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith().should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate user', function (done) {
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.authorization = 'Bearer ' + token;
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
registerUnsuccessfulBearerStrategy();
|
||||||
|
auth.authenticateUser(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate without bearer token', function (done) {
|
||||||
|
req.headers = {};
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
registerUnsuccessfulBearerStrategy();
|
||||||
|
auth.authenticateUser(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate with bearer token and client', function (done) {
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.authorization = 'Bearer ' + token;
|
||||||
|
req.client = {id: 1};
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
registerUnsuccessfulBearerStrategy();
|
||||||
|
auth.authenticateUser(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate when error', function (done) {
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.authorization = 'Bearer ' + token;
|
||||||
|
|
||||||
|
registerFaultyBearerStrategy();
|
||||||
|
auth.authenticateUser(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith('error').should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Client Authentication', function () {
|
||||||
|
it('shouldn\'t require authorized client with bearer token', function (done) {
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.authorization = 'Bearer ' + token;
|
||||||
|
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith().should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate client with broken bearer token', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.authorization = 'Bearer';
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate client without client_id/client_secret', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate client without client_id', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.body.client_secret = testSecret;
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate client without client_secret', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.body.client_id = testClient;
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate client', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.body.client_id = testClient;
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
registerUnsuccessfulClientPasswordStrategy();
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate client with invalid origin', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.body.client_id = testClient;
|
||||||
|
req.body.client_secret = testSecret;
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.origin = 'http://invalid.origin.com';
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'status', function (statusCode) {
|
||||||
|
statusCode.should.eql(401);
|
||||||
|
return {
|
||||||
|
json: function (err) {
|
||||||
|
err.errors[0].errorType.should.eql('UnauthorizedError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSuccessfulClientPasswordStrategy();
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
next.called.should.be.false;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate client', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.body.client_id = testClient;
|
||||||
|
req.body.client_secret = testSecret;
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.origin = config.url;
|
||||||
|
|
||||||
|
res.header = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'header', function (key, value) {
|
||||||
|
key.should.equal('Access-Control-Allow-Origin');
|
||||||
|
value.should.equal(config.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSuccessfulClientPasswordStrategy();
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith(null, client).should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate client without origin', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.body.client_id = testClient;
|
||||||
|
req.body.client_secret = testSecret;
|
||||||
|
|
||||||
|
res.header = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'header', function (key, value) {
|
||||||
|
key.should.equal('Access-Control-Allow-Origin');
|
||||||
|
value.should.equal(config.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSuccessfulClientPasswordStrategy();
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith(null, client).should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate client with id in query', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.query = {};
|
||||||
|
req.query.client_id = testClient;
|
||||||
|
req.query.client_secret = testSecret;
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.origin = config.url;
|
||||||
|
|
||||||
|
res.header = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'header', function (key, value) {
|
||||||
|
key.should.equal('Access-Control-Allow-Origin');
|
||||||
|
value.should.equal(config.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSuccessfulClientPasswordStrategy();
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith(null, client).should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should authenticate client with id + secret in query', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.query = {};
|
||||||
|
req.query.client_id = testClient;
|
||||||
|
req.query.client_secret = testSecret;
|
||||||
|
req.headers = {};
|
||||||
|
req.headers.origin = config.url;
|
||||||
|
|
||||||
|
res.header = {};
|
||||||
|
|
||||||
|
sandbox.stub(res, 'header', function (key, value) {
|
||||||
|
key.should.equal('Access-Control-Allow-Origin');
|
||||||
|
value.should.equal(config.url);
|
||||||
|
});
|
||||||
|
|
||||||
|
registerSuccessfulClientPasswordStrategy();
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith(null, client).should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shouldn\'t authenticate when error', function (done) {
|
||||||
|
req.body = {};
|
||||||
|
req.body.client_id = testClient;
|
||||||
|
req.body.client_secret = testSecret;
|
||||||
|
res.status = {};
|
||||||
|
|
||||||
|
registerFaultyClientPasswordStrategy();
|
||||||
|
auth.authenticateClient(req, res, next);
|
||||||
|
|
||||||
|
next.called.should.be.true;
|
||||||
|
next.calledWith('error').should.be.true;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,6 +9,7 @@ var _ = require('lodash'),
|
||||||
expectedProperties = {
|
expectedProperties = {
|
||||||
configuration: ['key', 'value'],
|
configuration: ['key', 'value'],
|
||||||
posts: ['posts', 'meta'],
|
posts: ['posts', 'meta'],
|
||||||
|
tags: ['tags', 'meta'],
|
||||||
users: ['users', 'meta'],
|
users: ['users', 'meta'],
|
||||||
roles: ['roles'],
|
roles: ['roles'],
|
||||||
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
|
pagination: ['page', 'limit', 'pages', 'total', 'next', 'prev'],
|
||||||
|
@ -75,11 +76,16 @@ function isISO8601(date) {
|
||||||
return moment(date).parsingFlags().iso;
|
return moment(date).parsingFlags().iso;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getURL() {
|
||||||
|
return schema + host;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getApiURL: getApiURL,
|
getApiURL: getApiURL,
|
||||||
getApiQuery: getApiQuery,
|
getApiQuery: getApiQuery,
|
||||||
getSigninURL: getSigninURL,
|
getSigninURL: getSigninURL,
|
||||||
getAdminURL: getAdminURL,
|
getAdminURL: getAdminURL,
|
||||||
|
getURL: getURL,
|
||||||
checkResponse: checkResponse,
|
checkResponse: checkResponse,
|
||||||
checkResponseValue: checkResponseValue,
|
checkResponseValue: checkResponseValue,
|
||||||
isISO8601: isISO8601
|
isISO8601: isISO8601
|
||||||
|
|
|
@ -377,7 +377,7 @@ DataGenerator.forKnex = (function () {
|
||||||
];
|
];
|
||||||
|
|
||||||
clients = [
|
clients = [
|
||||||
createBasic({name: 'Ghost Admin', slug: 'ghost-admin', secret: 'not_available'})
|
createBasic({name: 'Ghost Admin', slug: 'ghost-admin', secret: 'not_available', type: 'ua', status: 'enabled'})
|
||||||
];
|
];
|
||||||
|
|
||||||
roles_users = [
|
roles_users = [
|
||||||
|
|
|
@ -527,8 +527,14 @@ login = function login(request) {
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
request.post('/ghost/api/v0.1/authentication/token/')
|
request.post('/ghost/api/v0.1/authentication/token/')
|
||||||
.send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'})
|
.set('Origin', config.url)
|
||||||
.end(function (err, res) {
|
.send({
|
||||||
|
grant_type: 'password',
|
||||||
|
username: user.email,
|
||||||
|
password: user.password,
|
||||||
|
client_id: 'ghost-admin',
|
||||||
|
client_secret: 'not_available'
|
||||||
|
}).end(function (err, res) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return reject(err);
|
return reject(err);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue