Refactor policy util and error handling

Signed-off-by: Alexandre Bodin <bodin.alex@gmail.com>
This commit is contained in:
Alexandre Bodin 2020-01-23 16:59:50 +01:00
parent b414d213f2
commit 0b59bd61f6
8 changed files with 220 additions and 169 deletions

View File

@ -526,13 +526,19 @@ const formatModelConnectionsGQL = function(
}),
];
policyUtils.get(
'plugins.users-permissions.permissions',
plugin,
policiesFn,
`GraphQL connection "${name}" `,
name
);
try {
policiesFn.push(
policyUtils.get(
'plugins.users-permissions.permissions',
plugin,
name
)
);
} catch (error) {
strapi.stopWithError(
`Error building graphql connection "${queryName}": ${error.message}`
);
}
// Execute policies stack.
const policy = await compose(policiesFn)(ctx);

View File

@ -155,15 +155,15 @@ module.exports = {
}
// Populate policies.
policies.forEach(policy =>
policyUtils.get(
policy,
plugin,
policiesFn,
`GraphQL query "${queryName}"`,
name
)
);
policies.forEach(policyName => {
try {
policiesFn.push(policyUtils.get(policyName, plugin, name));
} catch (error) {
strapi.stopWithError(
`Error building graphql mutation "${queryName}": ${error.message}`
);
}
});
return async (obj, options, graphqlCtx) => {
const { context } = graphqlCtx;

View File

@ -227,15 +227,15 @@ module.exports = {
}
// Populate policies.
policies.forEach(policy =>
policyUtils.get(
policy,
plugin,
policiesFn,
`GraphQL query "${queryName}"`,
name
)
);
policies.forEach(policyName => {
try {
policiesFn.push(policyUtils.get(policyName, plugin, name));
} catch (error) {
strapi.stopWithError(
`Error building graphql query "${queryName}": ${error.message}`
);
}
});
return async (obj, options = {}, graphqlContext) => {
const { context } = graphqlContext;

View File

@ -0,0 +1,83 @@
'use strict';
const policyUtils = require('../policy');
describe('Policy util', () => {
describe('Get policy', () => {
test('Throws on policy not found', () => {
expect(() => policyUtils.get('undefined')).toThrow();
});
test('Retrieves global policy', () => {
const policyFn = () => {};
// init global strapi
global.strapi = {
config: {
policies: {
'test-policy': policyFn,
},
},
};
expect(policyUtils.get('global.test-policy')).toBe(policyFn);
});
test('Retrieves a global plugin policy', () => {
const policyFn = () => {};
global.strapi = {
plugins: {
'test-plugin': {
config: {
policies: {
'test-policy': policyFn,
},
},
},
},
};
expect(() => policyUtils.get('test-plugin.test-policy')).toThrow();
expect(policyUtils.get('plugins.test-plugin.test-policy')).toBe(policyFn);
});
test('Retrieves a plugin policy locally', () => {
const policyFn = () => {};
global.strapi = {
plugins: {
'test-plugin': {
config: {
policies: {
'test-policy': policyFn,
},
},
},
},
};
expect(policyUtils.get('test-policy', 'test-plugin')).toBe(policyFn);
});
test('Retrieves an api policy locally', () => {
const policyFn = () => {};
global.strapi = {
api: {
'test-api': {
config: {
policies: {
'test-policy': policyFn,
},
},
},
},
};
expect(policyUtils.get('test-policy', undefined, 'test-api')).toBe(
policyFn
);
});
});
});

View File

@ -1,121 +1,90 @@
// Public dependencies.
/**
* Policies util
*/
'use strict';
const _ = require('lodash');
/* eslint-disable prefer-template */
module.exports = {
get(policy, plugin, policies = [], endpoint, currentApiName) {
// Define global policy prefix.
const globalPolicyPrefix = 'global.';
const pluginPolicyPrefix = 'plugins.';
const policySplited = policy.split('.');
// Looking for global policy or namespaced.
if (
_.startsWith(policy, globalPolicyPrefix, 0) &&
!_.isUndefined(
_.get(
strapi.config.policies,
policy.replace(globalPolicyPrefix, '').toLowerCase()
)
)
) {
// Global policy.
return policies.push(
this.parsePolicy(
_.get(
strapi.config.policies,
policy.replace(globalPolicyPrefix, '').toLowerCase()
)
)
);
} else if (
_.startsWith(policy, pluginPolicyPrefix, 0) &&
strapi.plugins[policySplited[1]] &&
!_.isUndefined(
_.get(
strapi.plugins,
policySplited[1] +
'.config.policies.' +
policySplited[2].toLowerCase()
)
)
) {
// Plugin's policies can be used from app APIs with a specific syntax (`plugins.pluginName.policyName`).
return policies.push(
this.parsePolicy(
_.get(
strapi.plugins,
policySplited[1] +
'.config.policies.' +
policySplited[2].toLowerCase()
)
)
);
} else if (
!_.startsWith(policy, globalPolicyPrefix, 0) &&
plugin &&
!_.isUndefined(
_.get(
strapi.plugins,
plugin + '.config.policies.' + policy.toLowerCase()
)
)
) {
// Plugin policy used in the plugin itself.
return policies.push(
this.parsePolicy(
_.get(
strapi.plugins,
plugin + '.config.policies.' + policy.toLowerCase()
)
)
);
} else if (
!_.startsWith(policy, globalPolicyPrefix, 0) &&
!_.isUndefined(
_.get(
strapi.api,
currentApiName + '.config.policies.' + policy.toLowerCase()
)
)
) {
// API policy used in the API itself.
return policies.push(
this.parsePolicy(
_.get(
strapi.api,
currentApiName + '.config.policies.' + policy.toLowerCase()
)
)
);
}
const get = (policy, plugin, apiName) => {
if (globalPolicyExists(policy)) {
return parsePolicy(getGlobalPolicy(policy));
}
strapi.log.error(
`Ignored attempt to bind to ${endpoint} with unknown policy "${policy}"`
);
},
if (pluginPolicyExists(policy)) {
return parsePolicy(getPluginPolicy(policy));
}
parsePolicy(policy) {
if (_.isFunction(policy)) {
return policy;
}
const pluginPolicy = `${PLUGIN_PREFIX}${plugin}.${policy}`;
return policy.handler;
},
if (!isGlobal(policy) && plugin && pluginPolicyExists(pluginPolicy)) {
return parsePolicy(getPluginPolicy(pluginPolicy));
}
// Middleware used for every routes.
// Expose the endpoint in `this`.
globalPolicy({ method, endpoint, controller, action, plugin }) {
return async (ctx, next) => {
ctx.request.route = {
endpoint: `${method} ${endpoint}`,
controller: _.toLower(controller),
action: _.toLower(action),
splittedEndpoint: endpoint,
verb: _.toLower(method),
plugin,
};
const api = _.get(strapi.api, apiName);
if (!isGlobal(policy) && api && policyExistsIn(api, policy)) {
return parsePolicy(getPolicyIn(api, policy));
}
await next();
};
},
throw new Error(`Could not find policy "${policy}"`);
};
const globalPolicy = ({ method, endpoint, controller, action, plugin }) => {
return async (ctx, next) => {
ctx.request.route = {
endpoint: `${method} ${endpoint}`,
controller: _.toLower(controller),
action: _.toLower(action),
splittedEndpoint: endpoint,
verb: _.toLower(method),
plugin,
};
await next();
};
};
const parsePolicy = policy => {
if (_.isFunction(policy)) {
return policy;
}
return policy.handler;
};
const GLOBAL_PREFIX = 'global.';
const PLUGIN_PREFIX = 'plugins.';
const getPolicyIn = (container, policy) => {
return _.get(container, ['config', 'policies', policy.toLowerCase()]);
};
const policyExistsIn = (container, policy) => {
return !_.isUndefined(getPolicyIn(container, policy));
};
const isGlobal = policy => _.startsWith(policy, GLOBAL_PREFIX);
const getGlobalPolicy = policy => {
const strippedPolicy = policy.replace(GLOBAL_PREFIX, '');
return getPolicyIn(strapi, strippedPolicy);
};
const globalPolicyExists = policy => {
return isGlobal(policy) && !_.isUndefined(getGlobalPolicy(policy));
};
const getPluginPolicy = policy => {
const [, plugin = '', policyName = ''] = policy.split('.');
return getPolicyIn(_.get(strapi, ['plugins', plugin]), policyName);
};
const pluginPolicyExists = policy => {
return isPluginPolicy(policy) && !_.isUndefined(getPluginPolicy(policy));
};
const isPluginPolicy = policy => _.startsWith(policy, PLUGIN_PREFIX);
module.exports = {
get,
globalPolicy,
};

View File

@ -25,8 +25,7 @@ function createCoreApi({ api, model }) {
const controller = Object.assign(
createController({ service, model }),
userController,
{ identity: userController.identity || _.upperFirst(modelName) }
userController
);
return {

View File

@ -41,40 +41,34 @@ module.exports = strapi =>
controller
);
// Init policies array.
const policies = [];
// Add the `globalPolicy`.
policies.push(
policyUtils.globalPolicy({
controller: controllerKey,
action: actionName,
method,
endpoint,
plugin,
})
);
const globalPolicy = policyUtils.globalPolicy({
controller: controllerKey,
action: actionName,
method,
endpoint,
plugin,
});
// Init policies array.
const policies = [globalPolicy];
let policyOption = _.get(value, 'config.policies');
// Allow string instead of array of policies.
if (
!_.isArray(_.get(value, 'config.policies')) &&
!_.isEmpty(_.get(value, 'config.policies'))
) {
value.config.policies = [value.config.policies];
if (_.isString(policyOption) && !_.isEmpty(policyOption)) {
policyOption = [policyOption];
}
if (
_.isArray(_.get(value, 'config.policies')) &&
!_.isEmpty(_.get(value, 'config.policies'))
) {
_.forEach(value.config.policies, policy => {
policyUtils.get(
policy,
plugin,
policies,
`${method} ${endpoint}`,
currentApiName
);
if (_.isArray(policyOption)) {
policyOption.forEach(policyName => {
try {
policies.push(policyUtils.get(policyName, plugin, currentApiName));
} catch (error) {
strapi.stopWithError(
`Error creating endpoint ${method} ${endpoint}: ${error.message}`
);
}
});
}