Jelmer Visser f0e8df63c5 Fix custom mutation returning null and not executing policies (#3735)
Fix custom mutation returning null and not executing policies
2019-08-13 10:55:12 +02:00

251 lines
6.7 KiB
JavaScript

'use strict';
/**
* Mutation.js service
*
* @description: A set of functions similar to controller's actions to avoid code duplication.
*/
const _ = require('lodash');
const pluralize = require('pluralize');
const compose = require('koa-compose');
const policyUtils = require('strapi-utils').policy;
const Query = require('./Query.js');
module.exports = {
/**
* Execute policies before the specified resolver.
*
* @return Promise or Error.
*/
composeMutationResolver: function(_schema, plugin, name, action) {
// Extract custom resolver or type description.
const { resolver: handler = {} } = _schema;
const queryName = `${action}${_.capitalize(name)}`;
// Retrieve policies.
const policies = _.get(handler, `Mutation.${queryName}.policies`, []);
// Retrieve resolverOf.
const resolverOf = _.get(handler, `Mutation.${queryName}.resolverOf`, '');
const policiesFn = [];
// Boolean to define if the resolver is going to be a resolver or not.
let isController = false;
// Retrieve resolver. It could be the custom resolver of the user
// or the shadow CRUD resolver (aka Content-Manager).
const resolver = (() => {
// Try to retrieve custom resolver.
const resolver = _.get(handler, `Mutation.${queryName}.resolver`);
if (_.isString(resolver) || _.isPlainObject(resolver)) {
const { handler = resolver } = _.isPlainObject(resolver)
? resolver
: {};
// Retrieve the controller's action to be executed.
const [name, action] = handler.split('.');
const controller = plugin
? _.get(
strapi.plugins,
`${plugin}.controllers.${_.toLower(name)}.${action}`
)
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
if (!controller) {
return new Error(
`Cannot find the controller's action ${name}.${action}`
);
}
// We're going to return a controller instead.
isController = true;
// Push global policy to make sure the permissions will work as expected.
policiesFn.push(
policyUtils.globalPolicy(
undefined,
{
handler: `${name}.${action}`,
},
undefined,
plugin
)
);
// Return the controller.
return controller;
} else if (resolver) {
// Function.
return resolver;
}
// We're going to return a controller instead.
isController = true;
const controllers = plugin
? strapi.plugins[plugin].controllers
: strapi.controllers;
// Try to find the controller that should be related to this model.
const controller = _.get(controllers, `${name}.${action}`);
if (!controller) {
return new Error(
`Cannot find the controller's action ${name}.${action}`
);
}
// Push global policy to make sure the permissions will work as expected.
// We're trying to detect the controller name.
policiesFn.push(
policyUtils.globalPolicy(
undefined,
{
handler: `${name}.${action}`,
},
undefined,
plugin
)
);
// Make the query compatible with our controller by
// setting in the context the parameters.
return async (ctx, next) => {
return controller(ctx, next);
};
})();
// The controller hasn't been found.
if (_.isError(resolver)) {
return resolver;
}
// Force policies of another action on a custom resolver.
if (_.isString(resolverOf) && !_.isEmpty(resolverOf)) {
// Retrieve the controller's action to be executed.
const [name, action] = resolverOf.split('.');
const controller = plugin
? _.get(
strapi.plugins,
`${plugin}.controllers.${_.toLower(name)}.${action}`
)
: _.get(strapi.controllers, `${_.toLower(name)}.${action}`);
if (!controller) {
return new Error(
`Cannot find the controller's action ${name}.${action}`
);
}
policiesFn[0] = policyUtils.globalPolicy(
undefined,
{
handler: `${name}.${action}`,
},
undefined,
plugin
);
}
if (strapi.plugins['users-permissions']) {
policies.push('plugins.users-permissions.permissions');
}
// Populate policies.
policies.forEach(policy =>
policyUtils.get(
policy,
plugin,
policiesFn,
`GraphQL query "${queryName}"`,
name
)
);
return async (obj, options, { context }) => {
// Hack to be able to handle permissions for each query.
const ctx = Object.assign(_.clone(context), {
request: Object.assign(_.clone(context.request), {
graphql: null,
}),
});
// Execute policies stack.
const policy = await compose(policiesFn)(ctx);
// Policy doesn't always return errors but they update the current context.
if (
_.isError(ctx.request.graphql) ||
_.get(ctx.request.graphql, 'isBoom')
) {
return ctx.request.graphql;
}
// Something went wrong in the policy.
if (policy) {
return policy;
}
// Resolver can be a function. Be also a native resolver or a controller's action.
if (_.isFunction(resolver)) {
const normalizedName = _.toLower(name);
let primaryKey;
if (plugin) {
primaryKey = strapi.plugins[plugin].models[normalizedName].primaryKey;
} else {
primaryKey = strapi.models[normalizedName].primaryKey;
}
if (options.input && options.input.where) {
context.params = Query.convertToParams(
options.input.where || {},
primaryKey
);
} else {
context.params = {};
}
if (options.input && options.input.data) {
context.request.body = options.input.data || {};
} else {
context.request.body = options;
}
if (isController) {
const values = await resolver.call(null, context);
if (ctx.body) {
return options.input
? {
[pluralize.singular(normalizedName)]: ctx.body,
}
: ctx.body;
}
const body = values && values.toJSON ? values.toJSON() : values;
return options.input
? {
[pluralize.singular(normalizedName)]: body,
}
: body;
}
return resolver.call(null, obj, options, context);
}
// Resolver can be a promise.
return resolver;
};
},
};