Apply policy for each query and use generated API business logic

This commit is contained in:
Aurelsicoko 2018-04-10 11:47:01 +02:00
parent 0f581fa5c3
commit 3daf7523c8
6 changed files with 132 additions and 60 deletions

View File

@ -95,6 +95,8 @@ module.exports = strapi => {
resolver
});
}, strapi.plugins.graphql.config._schema.graphql);
const { Query = {}, Mutation = {} } = _.get(strapi.plugins.graphql, 'config._schema.graphql.resolver', {});
},
initialize: function(cb) {

View File

@ -50,4 +50,4 @@
"npm": ">= 3.0.0"
},
"license": "MIT"
}
}

View File

@ -16,7 +16,6 @@ const graphql = require('graphql');
const { makeExecutableSchema } = require('graphql-tools');
const GraphQLJSON = require('graphql-type-json');
module.exports = {
/**
@ -174,6 +173,10 @@ module.exports = {
const queryOpts = plugin ? { source: plugin } : {};
const model = plugin ?
strapi.plugins[plugin].models[name]:
strapi.models[name];
// Retrieve generic service from the Content Manager plugin.
const resolvers = strapi.plugins['content-manager'].services['contentmanager'];
@ -186,58 +189,105 @@ module.exports = {
// Retrieve policies.
const policies = isSingular ?
_.get(handler, `Query.${pluralize.singular(name)}.policy`, []):
_.get(handler, `Query.${pluralize.plural(name)}.policy`, []);
_.get(handler, `Query.${pluralize.singular(name)}.policies`, []):
_.get(handler, `Query.${pluralize.plural(name)}.policies`, []);
// 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 = (() => {
if (isSingular) {
return _.get(handler, `Query.${pluralize.singular(name)}.resolver`,
async () => {
const value = await resolvers.fetch({ ...params, id: options.id }, plugin, []);
// Try to retrieve custom resolver.
const resolver = isSingular ?
_.get(handler, `Query.${pluralize.singular(name)}.resolver`):
_.get(handler, `Query.${pluralize.plural(name)}.resolver`);
return value.toJSON ? value.toJSON() : value;
}
);
if (resolver) {
return resolver;
}
const resolver = _.get(handler, `Query.${pluralize.plural(name)}.resolver`,
async () => {
const convertedParams = strapi.utils.models.convertParams(name, this.convertToParams(options));
const where = strapi.utils.models.convertParams(name, options.where || {});
// We're going to return a controller instead.
isController = true;
// Content-Manager specificity.
convertedParams.skip = convertedParams.start;
convertedParams.query = where.where;
// Try to find the controller that should be related to this model.
const controller = isSingular ?
_.get(strapi.controllers, `${name}.findOne`):
_.get(strapi.controllers, `${name}.find`);
const value = await resolvers.fetchAll(params, { ...queryOpts, ...convertedParams, populate: [] });
if (!controller) {
return new Error(`Cannot find the controller's action ${name}.${isSingular ? 'findOne' : 'find'}`);
}
return value.toJSON ? value.toJSON() : value;
// Make the query compatible with our controller by
// setting in the context the parameters.
if (isSingular) {
return async (ctx, next) => {
ctx.params = {
...params,
[model.primaryKey]: options.id
};
// Return the controller.
return controller(ctx, next);
}
);
}
return resolver;
// Plural.
return async (ctx, next) => {
ctx.query = this.convertToParams(options);
// console.log(strapi.utils.models.convertParams(name, options.where || {}));
return controller(ctx, next);
}
})();
const policiesFn = [];
// 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}.${isSingular ? 'findOne' : 'find'}`
}, 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));
// Hack to be able to handle permissions for each query.
const ctx = Object.assign(_.clone(context), {
request: {
graphql: null
}
});
// Execute policies stack.
const policy = await strapi.koaMiddlewares.compose(policiesFn)(context);
const policy = await strapi.koaMiddlewares.compose(policiesFn)(ctx);
// Policy doesn't always return errors but they update the current context.
if (_.isError(context.response.body) || _.get(context.response.body, 'isBoom')) {
return context.response.body;
if (_.isError(ctx.request.graphql) || _.get(ctx.request.graphql, 'isBoom')) {
return ctx.request.graphql;
}
// When everything is okay, the policy variable should be undefined
// so it will return the resolver instead.
// Something went wrong in the policy.
if (policy) {
return policy;
}
// Note: The resolver can be a function or promise.
return policy || _.isFunction(resolver) ? resolver.call(null, obj, options, context) : resolver;
// Resolver can be a function. Be also a native resolver or a controller's action.
if (_.isFunction(resolver)) {
return isController ?
resolver.call(null, context):
resolver.call(null, obj, options, context);
}
// Resolver can be a promise.
return resolver;
},
/**
@ -503,9 +553,6 @@ module.exports = {
// Write schema.
this.writeGenerateSchema(graphql.printSchema(schema));
// Temporary variable to store the entire GraphQL configuration.
delete strapi.plugins.graphql.config._schema.graphql;
return schema;
},

View File

@ -39,7 +39,9 @@ module.exports = async (ctx, next) => {
}, []);
if (!permission) {
return ctx.unauthorized();
ctx.unauthorized();
return ctx.request.graphql = ctx.body;
}
// Execute the policies.

View File

@ -3,7 +3,7 @@
const _ = require('lodash');
module.exports = {
get: (policy, plugin, policies = [], endpoint, currentApiName) => {
get: function (policy, plugin, policies = [], endpoint, currentApiName) {
// Define global policy prefix.
const globalPolicyPrefix = 'global.';
const pluginPolicyPrefix = 'plugins.';
@ -19,9 +19,9 @@ module.exports = {
) {
// Global policy.
return policies.push(
strapi.config.policies[
this.parsePolicy(strapi.config.policies[
policy.replace(globalPolicyPrefix, '').toLowerCase()
]
])
);
} else if (
_.startsWith(policy, pluginPolicyPrefix, 0) &&
@ -37,12 +37,12 @@ module.exports = {
) {
// Plugin's policies can be used from app APIs with a specific syntax (`plugins.pluginName.policyName`).
return policies.push(
_.get(
this.parsePolicy(_.get(
strapi.plugins,
policySplited[1] +
'.config.policies.' +
policySplited[2].toLowerCase()
)
))
);
} else if (
!_.startsWith(policy, globalPolicyPrefix, 0) &&
@ -56,10 +56,10 @@ module.exports = {
) {
// Plugin policy used in the plugin itself.
return policies.push(
_.get(
this.parsePolicy(_.get(
strapi.plugins,
plugin + '.config.policies.' + policy.toLowerCase()
)
))
);
} else if (
!_.startsWith(policy, globalPolicyPrefix, 0) &&
@ -72,13 +72,40 @@ module.exports = {
) {
// API policy used in the API itself.
return policies.push(
_.get(
this.parsePolicy(_.get(
strapi.api,
currentApiName + '.config.policies.' + policy.toLowerCase()
)
))
);
}
strapi.log.error(`Ignored attempt to bind to ${endpoint} with unknown policy "${policy}"`);
},
parsePolicy: (policy) => {
if (_.isFunction(policy)) {
return policy;
}
return policy.handler;
},
// Middleware used for every routes.
// Expose the endpoint in `this`.
globalPolicy: (endpoint, value, route = {}, plugin) => {
return async (ctx, next) => {
ctx.request.route = {
endpoint: _.trim(endpoint),
controller: value.handler.split('.')[0].toLowerCase(),
action: value.handler.split('.')[1].toLowerCase(),
splittedEndpoint: _.trim(route.endpoint),
verb: route.verb && _.trim(route.verb.toLowerCase()),
plugin
};
console.log("CTX", ctx.request.route);
await next();
};
}
};

View File

@ -13,23 +13,6 @@ const regex = require('strapi-utils').regex;
const joijson = require('strapi-utils').joijson;
const policyUtils = require('strapi-utils').policy;
// Middleware used for every routes.
// Expose the endpoint in `this`.
function globalPolicy(endpoint, value, route, plugin) {
return async (ctx, next) => {
ctx.request.route = {
endpoint: _.trim(endpoint),
controller: value.handler.split('.')[0].toLowerCase(),
action: value.handler.split('.')[1].toLowerCase(),
splittedEndpoint: _.trim(route.endpoint),
verb: route.verb && _.trim(route.verb.toLowerCase()),
plugin
};
await next();
};
}
module.exports = strapi => function routerChecker(value, endpoint, plugin) {
const Joi = strapi.koaMiddlewares.routerJoi.Joi;
const builder = joijson.builder(Joi);
@ -52,7 +35,7 @@ module.exports = strapi => function routerChecker(value, endpoint, plugin) {
const policies = [];
// Add the `globalPolicy`.
policies.push(globalPolicy(endpoint, value, route, plugin));
policies.push(policyUtils.globalPolicy(endpoint, value, route, plugin));
// Allow string instead of array of policies.
if (
@ -71,6 +54,17 @@ module.exports = strapi => function routerChecker(value, endpoint, plugin) {
});
}
policies.push(async (ctx, next) => {
// Set body.
console.log("coucou");
const values = await next();
if (!ctx.body) {
ctx.body = values
}
console.log(ctx.body);
});
// Init validate.
const validate = {};