Merge branch 'releases/v4' of https://github.com/strapi/strapi into fix-filter-popover-date

This commit is contained in:
ronronscelestes 2021-11-08 12:22:51 +01:00
commit 5ec0e78a5d
14 changed files with 127 additions and 110 deletions

View File

@ -1,3 +1,3 @@
module.exports = ctx => { module.exports = (policyCtx, config, { strapi }) => {
return true; return true;
}; };

View File

@ -8,7 +8,7 @@ module.exports = {
handler: 'address.find', handler: 'address.find',
config: { config: {
middlewares: ['api::address.address-middleware'], middlewares: ['api::address.address-middleware'],
policies: ['address'], policies: ['global::test-policy', 'api::address.address'],
}, },
}, },
{ {

View File

@ -7,7 +7,7 @@
module.exports = (config, { strapi }) => { module.exports = (config, { strapi }) => {
// Add your own logic here. // Add your own logic here.
return async (ctx, next) => { return async (ctx, next) => {
strapi.log.info('In test-middleware middleware.'); strapi.log.info('In application test-middleware middleware.');
await next(); await next();
}; };

View File

@ -1,3 +1,18 @@
module.exports = ctx => { 'use strict';
return true;
/**
* `test-policy` policy.
*/
module.exports = (policyCtx, config, { strapi }) => {
// Add your own logic here.
strapi.log.info('In test-policy policy.');
const canDoSomething = true;
if (canDoSomething) {
return true;
}
return false;
}; };

View File

@ -1,9 +1,7 @@
'use strict'; 'use strict';
const _ = require('lodash'); const _ = require('lodash');
const { const { createPolicy } = require('@strapi/utils').policy;
policy: { createPolicyFactory },
} = require('@strapi/utils');
const { validateHasPermissionsInput } = require('../validation/policies/hasPermissions'); const { validateHasPermissionsInput } = require('../validation/policies/hasPermissions');
const inputModifiers = [ const inputModifiers = [
@ -22,28 +20,24 @@ const inputModifiers = [
}, },
]; ];
module.exports = createPolicyFactory( module.exports = createPolicy({
config => { name: 'admin::hasPermissions',
validator: validateHasPermissionsInput,
handler(ctx, config) {
const { actions } = config; const { actions } = config;
const permissions = actions.map(action => const permissions = actions.map(action =>
inputModifiers.find(modifier => modifier.check(action)).transform(action) inputModifiers.find(modifier => modifier.check(action)).transform(action)
); );
return ctx => { const { userAbility: ability, isAuthenticated } = ctx.state;
const { userAbility: ability, isAuthenticated } = ctx.state;
if (!isAuthenticated || !ability) { if (!isAuthenticated || !ability) {
return true; return true;
} }
const isAuthorized = permissions.every(({ action, subject }) => ability.can(action, subject)); const isAuthorized = permissions.every(({ action, subject }) => ability.can(action, subject));
return isAuthorized; return isAuthorized;
};
}, },
{ });
validator: validateHasPermissionsInput,
name: 'admin::hasPermissions',
}
);

View File

@ -1,5 +1,5 @@
'use strict'; 'use strict';
module.exports = ctx => { module.exports = policyCtx => {
return Boolean(ctx.state.isAuthenticated); return Boolean(policyCtx.state.isAuthenticated);
}; };

View File

@ -29,7 +29,7 @@ describe('hasDraftAndPublish policy', () => {
test('It should succeed when the model has draft & publish enabled', () => { test('It should succeed when the model has draft & publish enabled', () => {
const ctx = { params: { model: 'foo' } }; const ctx = { params: { model: 'foo' } };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi }); const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(true); expect(res).toBe(true);
}); });
@ -37,21 +37,21 @@ describe('hasDraftAndPublish policy', () => {
test(`It should fail when the model has draft & publish disabled`, () => { test(`It should fail when the model has draft & publish disabled`, () => {
const ctx = { params: { model: 'bar' } }; const ctx = { params: { model: 'bar' } };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi }); const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(false); expect(res).toBe(false);
}); });
test(`It should fail when the model doesn't exists`, () => { test(`It should fail when the model doesn't exists`, () => {
const ctx = { params: { model: 'foobar' } }; const ctx = { params: { model: 'foobar' } };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi }); const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(false); expect(res).toBe(false);
}); });
test(`It should fail when params.model isn't provided`, () => { test(`It should fail when params.model isn't provided`, () => {
const ctx = { params: {} }; const ctx = { params: {} };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi }); const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(false); expect(res).toBe(false);
}); });
}); });

View File

@ -4,7 +4,7 @@ const {
contentTypes: { hasDraftAndPublish }, contentTypes: { hasDraftAndPublish },
} = require('@strapi/utils'); } = require('@strapi/utils');
module.exports = (ctx, { strapi }) => { module.exports = (ctx, config, { strapi }) => {
const { model: modelUID } = ctx.params; const { model: modelUID } = ctx.params;
const model = strapi.contentTypes[modelUID]; const model = strapi.contentTypes[modelUID];

View File

@ -1,12 +1,16 @@
'use strict'; 'use strict';
const { const {
policy: { createPolicyFactory }, policy: { createPolicy },
} = require('@strapi/utils'); } = require('@strapi/utils');
const { validateHasPermissionsInput } = require('../validation/policies/hasPermissions'); const { validateHasPermissionsInput } = require('../validation/policies/hasPermissions');
module.exports = createPolicyFactory( module.exports = createPolicy({
({ actions = [], hasAtLeastOne = false } = {}) => ctx => { name: 'plugin::content-manager.hasPermissions',
validator: validateHasPermissionsInput,
handler(ctx, config = {}) {
const { actions = [], hasAtLeastOne = false } = config;
const { const {
state: { userAbility, isAuthenticatedAdmin }, state: { userAbility, isAuthenticatedAdmin },
params: { model }, params: { model },
@ -22,8 +26,4 @@ module.exports = createPolicyFactory(
return isAuthorized; return isAuthorized;
}, },
{ });
validator: validateHasPermissionsInput,
name: 'plugin::content-manager.hasPermissions',
}
);

View File

@ -82,7 +82,8 @@ module.exports = strapi => {
router[method](path, routeHandler); router[method](path, routeHandler);
} catch (error) { } catch (error) {
throw new Error(`Error creating endpoint ${route.method} ${route.path}: ${error.message}`); error.message = `Error creating endpoint ${route.method} ${route.path}: ${error.message}`;
throw error;
} }
}; };
}; };

View File

@ -1,24 +1,20 @@
'use strict'; 'use strict';
const { propOr } = require('lodash/fp'); const { propOr } = require('lodash/fp');
const policy = require('@strapi/utils/lib/policy');
const { ForbiddenError } = require('@strapi/utils').errors; const { ForbiddenError } = require('@strapi/utils').errors;
const { policy: policyUtils } = require('@strapi/utils');
const getPoliciesConfig = propOr([], 'config.policies'); const getPoliciesConfig = propOr([], 'config.policies');
const resolvePolicies = route => { const resolvePolicies = route => {
const { pluginName, apiName } = route.info || {};
const policiesConfig = getPoliciesConfig(route); const policiesConfig = getPoliciesConfig(route);
const resolvedPolicies = policyUtils.resolve(policiesConfig, route.info);
const resolvedPolicies = policiesConfig.map(policyConfig =>
policy.get(policyConfig, { pluginName, apiName })
);
const policiesMiddleware = async (ctx, next) => { const policiesMiddleware = async (ctx, next) => {
const context = policy.createPolicyContext('koa', ctx); const context = policyUtils.createPolicyContext('koa', ctx);
for (const resolvedPolicy of resolvedPolicies) { for (const { handler, config } of resolvedPolicies) {
const result = await resolvedPolicy(context, { strapi }); const result = await handler(context, config, { strapi });
if (![true, undefined].includes(result)) { if (![true, undefined].includes(result)) {
throw new ForbiddenError('Policies failed.'); throw new ForbiddenError('Policies failed.');

View File

@ -9,36 +9,22 @@ const { eq } = require('lodash/fp');
const PLUGIN_PREFIX = 'plugin::'; const PLUGIN_PREFIX = 'plugin::';
const API_PREFIX = 'api::'; const API_PREFIX = 'api::';
const createPolicy = (policyName, config) => ({ policyName, config });
const resolveHandler = policy => {
return _.has('handler', policy) ? policy.handler : policy;
};
const parsePolicy = policy => { const parsePolicy = policy => {
if (typeof policy === 'string') { if (typeof policy === 'string') {
return createPolicy(policy); return { policyName: policy, config: {} };
} }
const { name, config = {} } = policy; const { name, config } = policy;
return createPolicy(name, config); return { policyName: name, config };
};
const resolvePolicy = policyName => {
const policy = strapi.policy(policyName);
return resolveHandler(policy);
}; };
const searchLocalPolicy = (policyName, { pluginName, apiName }) => { const searchLocalPolicy = (policyName, { pluginName, apiName }) => {
if (pluginName) { if (pluginName) {
const policy = strapi.policy(`${PLUGIN_PREFIX}${pluginName}.${policyName}`); return strapi.policy(`${PLUGIN_PREFIX}${pluginName}.${policyName}`);
return resolveHandler(policy);
} }
if (apiName) { if (apiName) {
const policy = strapi.policy(`${API_PREFIX}${apiName}.${policyName}`); return strapi.policy(`${API_PREFIX}${apiName}.${policyName}`);
return resolveHandler(policy);
} }
}; };
@ -56,45 +42,68 @@ const globalPolicy = ({ method, endpoint, controller, action, plugin }) => {
}; };
}; };
const get = (policy, { pluginName, apiName } = {}) => { const resolvePolicies = (config, { pluginName, apiName } = {}) => {
if (typeof policy === 'function') { return config.map(policyConfig => {
return policy; return {
} handler: getPolicy(policyConfig, { pluginName, apiName }),
config: policyConfig.config || {},
};
});
};
const { policyName, config } = parsePolicy(policy); const findPolicy = (name, { pluginName, apiName } = {}) => {
const resolvedPolicy = strapi.policy(name);
const resolvedPolicy = resolvePolicy(policyName);
if (resolvedPolicy !== undefined) { if (resolvedPolicy !== undefined) {
return _.isPlainObject(policy) ? resolvedPolicy(config) : resolvedPolicy; return resolvedPolicy;
} }
const localPolicy = searchLocalPolicy(policy, { pluginName, apiName }); const localPolicy = searchLocalPolicy(name, { pluginName, apiName });
if (localPolicy !== undefined) { if (localPolicy !== undefined) {
return localPolicy; return localPolicy;
} }
throw new Error(`Could not find policy "${policy}"`); throw new Error(`Could not find policy "${name}"`);
}; };
const createPolicyFactory = (factoryCallback, options) => { const getPolicy = (policyConfig, { pluginName, apiName } = {}) => {
const { validator, name = 'unnamed' } = options; if (typeof policyConfig === 'function') {
return policyConfig;
}
const validate = (...args) => { const { policyName, config } = parsePolicy(policyConfig);
try {
validator(...args); const policy = findPolicy(policyName, { pluginName, apiName });
} catch (e) {
throw new Error(`Invalid objects submitted to "${name}" policy.`); if (typeof policy === 'function') {
return policy;
}
if (policy.validator) {
policy.validator(config);
}
return policy.handler;
};
const createPolicy = options => {
const { name = 'unnamed', validator, handler } = options;
const wrappedValidator = config => {
if (validator) {
try {
validator(config);
} catch (e) {
throw new Error(`Invalid config passed to "${name}" policy.`);
}
} }
}; };
return config => { return {
if (validator) { name,
validate(config); validator: wrappedValidator,
} handler,
return factoryCallback(config);
}; };
}; };
@ -102,7 +111,6 @@ const createPolicyContext = (type, ctx) => {
return Object.assign( return Object.assign(
{ {
is: eq(type), is: eq(type),
get type() { get type() {
return type; return type;
}, },
@ -112,8 +120,9 @@ const createPolicyContext = (type, ctx) => {
}; };
module.exports = { module.exports = {
get, get: getPolicy,
resolve: resolvePolicies,
globalPolicy, globalPolicy,
createPolicyFactory, createPolicy,
createPolicyContext, createPolicyContext,
}; };

View File

@ -4,15 +4,17 @@
* `{{id}}` policy. * `{{id}}` policy.
*/ */
module.exports = async (ctx) => { module.exports = (config, { strapi }) => {
// Add your own logic here. return policyCtx => {
console.log('In {{id}} policy.'); // Add your own logic here.
strapi.log.info('In {{id}} policy.');
const canDoSomething = true; const canDoSomething = true;
if (canDoSomething) { if (canDoSomething) {
return true; return true;
} }
return false; return false;
};
}; };

View File

@ -1,25 +1,25 @@
'use strict'; 'use strict';
const { getOr } = require('lodash/fp'); const { propOr } = require('lodash/fp');
const { policy: policyUtils } = require('@strapi/utils'); const { policy: policyUtils } = require('@strapi/utils');
const { ForbiddenError } = require('@strapi/utils').errors; const { ForbiddenError } = require('@strapi/utils').errors;
const getPoliciesConfig = propOr([], 'policies');
const createPoliciesMiddleware = (resolverConfig, { strapi }) => { const createPoliciesMiddleware = (resolverConfig, { strapi }) => {
const resolverPolicies = getPoliciesConfig(resolverConfig);
const policies = policyUtils.resolve(resolverPolicies);
return async (resolve, ...rest) => { return async (resolve, ...rest) => {
const resolverPolicies = getOr([], 'policies', resolverConfig);
// Transform every policy into a unique format
const policies = resolverPolicies.map(policy => policyUtils.get(policy));
// Create a graphql policy context // Create a graphql policy context
const context = createGraphQLPolicyContext(...rest); const context = createGraphQLPolicyContext(...rest);
// Run policies & throw an error if one of them fails // Run policies & throw an error if one of them fails
for (const policy of policies) { for (const { handler, config } of policies) {
const result = await policy(context, { strapi }); const result = await handler(context, config, { strapi });
if (![true, undefined].includes(result)) { if (![true, undefined].includes(result)) {
throw new ForbiddenError(); throw new ForbiddenError('Policies failed.');
} }
} }