mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
d246a4761e
no-issue This adds two new endpoints, one at /ghost/.well-known/jwks.json for exposing a public key, and one on the canary api /identities, which allows the Owner user to fetch a JWT. This token can then be used by external services to verify the domain * Added ghost_{public,private}_key settings This key can be used for generating tokens for communicating with external services on behalf of Ghost * Added .well-known directory to /ghost/.well-known We add a jwks.json file to the .well-known directory which exposes a public JWK which can be used to verify the signatures of JWT's created by Ghost This is added to the /ghost/ path so that it can live on the admin domain, rather than the frontend. This is because most of its uses/functions will be in relation to the admin domain. * Improved settings model tests This removes hardcoded positions in favour of testing that a particular event wasn't emitted which is less brittle and more precise about what's being tested * Fixed parent app unit tests for well-known This updates the parent app unit tests to check that the well-known route is mounted. We all change proxyquire to use `noCallThru` which ensures that the ubderlying modules are not required. This stops the initialisation logic in ./well-known erroring in tests https://github.com/thlorenz/proxyquire/issues/215 * Moved jwt signature to a separate 'token' propery This structure corresponds to other resources and allows to exptend with additional properties in future if needed
111 lines
4.2 KiB
JavaScript
111 lines
4.2 KiB
JavaScript
const debug = require('ghost-ignition').debug('api:canary:utils:permissions');
|
|
const Promise = require('bluebird');
|
|
const _ = require('lodash');
|
|
const permissions = require('../../../services/permissions');
|
|
const common = require('../../../lib/common');
|
|
|
|
/**
|
|
* @description Handle requests, which need authentication.
|
|
*
|
|
* @param {Object} apiConfig - Docname & method of API ctrl
|
|
* @param {Object} frame
|
|
* @return {Promise}
|
|
*/
|
|
const nonePublicAuth = (apiConfig, frame) => {
|
|
debug('check admin permissions');
|
|
|
|
let singular;
|
|
if (apiConfig.docName.match(/ies$/)) {
|
|
singular = apiConfig.docName.replace(/ies$/, 'y');
|
|
} else {
|
|
singular = apiConfig.docName.replace(/s$/, '');
|
|
}
|
|
let permissionIdentifier = frame.options.id;
|
|
|
|
// CASE: Target ctrl can override the identifier. The identifier is the unique identifier of the target resource
|
|
// e.g. edit a setting -> the key of the setting
|
|
// e.g. edit a post -> post id from url param
|
|
// e.g. change user password -> user id inside of the body structure
|
|
if (apiConfig.identifier) {
|
|
permissionIdentifier = apiConfig.identifier(frame);
|
|
}
|
|
|
|
let unsafeAttrObject = apiConfig.unsafeAttrs && _.has(frame, `data.[${apiConfig.docName}][0]`) ? _.pick(frame.data[apiConfig.docName][0], apiConfig.unsafeAttrs) : {};
|
|
|
|
if (apiConfig.unsafeAttrsObject) {
|
|
unsafeAttrObject = apiConfig.unsafeAttrsObject(frame);
|
|
}
|
|
|
|
const permsPromise = permissions.canThis(frame.options.context)[apiConfig.method][singular](permissionIdentifier, unsafeAttrObject);
|
|
|
|
return permsPromise.then((result) => {
|
|
/*
|
|
* Allow the permissions function to return a list of excluded attributes.
|
|
* If it does, omit those attrs from the data passed through
|
|
*
|
|
* NOTE: excludedAttrs differ from unsafeAttrs in that they're determined by the model's permissible function,
|
|
* and the attributes are simply excluded rather than throwing a NoPermission exception
|
|
*
|
|
* TODO: This is currently only needed because of the posts model and the contributor role. Once we extend the
|
|
* contributor role to be able to edit existing tags, this concept can be removed.
|
|
*/
|
|
if (result && result.excludedAttrs && _.has(frame, `data.[${apiConfig.docName}][0]`)) {
|
|
frame.data[apiConfig.docName][0] = _.omit(frame.data[apiConfig.docName][0], result.excludedAttrs);
|
|
}
|
|
}).catch((err) => {
|
|
if (err instanceof common.errors.NoPermissionError) {
|
|
err.message = common.i18n.t('errors.api.utils.noPermissionToCall', {
|
|
method: apiConfig.method,
|
|
docName: apiConfig.docName
|
|
});
|
|
return Promise.reject(err);
|
|
}
|
|
|
|
if (common.errors.utils.isIgnitionError(err)) {
|
|
return Promise.reject(err);
|
|
}
|
|
|
|
return Promise.reject(new common.errors.GhostError({
|
|
err: err
|
|
}));
|
|
});
|
|
};
|
|
|
|
// @TODO: https://github.com/TryGhost/Ghost/issues/10735
|
|
module.exports = {
|
|
/**
|
|
* @description Handle permission stage for canary API.
|
|
*
|
|
* @param {Object} apiConfig - Docname & method of target ctrl.
|
|
* @param {Object} frame
|
|
* @return {Promise}
|
|
*/
|
|
handle(apiConfig, frame) {
|
|
debug('handle');
|
|
|
|
// @TODO: https://github.com/TryGhost/Ghost/issues/10099
|
|
frame.options.context = permissions.parseContext(frame.options.context);
|
|
|
|
// CASE: Content API access
|
|
if (frame.options.context.public) {
|
|
debug('check content permissions');
|
|
|
|
// @TODO: Remove when we drop v0.1
|
|
// @TODO: https://github.com/TryGhost/Ghost/issues/10733
|
|
return permissions.applyPublicRules(apiConfig.docName, apiConfig.method, {
|
|
status: frame.options.status,
|
|
id: frame.options.id,
|
|
uuid: frame.options.uuid,
|
|
slug: frame.options.slug,
|
|
data: {
|
|
status: frame.data.status,
|
|
id: frame.data.id,
|
|
uuid: frame.data.uuid,
|
|
slug: frame.data.slug
|
|
}
|
|
});
|
|
}
|
|
|
|
return nonePublicAuth(apiConfig, frame);
|
|
}
|
|
};
|