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;
};

View File

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

View File

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

View File

@ -1,3 +1,18 @@
module.exports = ctx => {
return true;
'use strict';
/**
* `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';
const _ = require('lodash');
const {
policy: { createPolicyFactory },
} = require('@strapi/utils');
const { createPolicy } = require('@strapi/utils').policy;
const { validateHasPermissionsInput } = require('../validation/policies/hasPermissions');
const inputModifiers = [
@ -22,28 +20,24 @@ const inputModifiers = [
},
];
module.exports = createPolicyFactory(
config => {
module.exports = createPolicy({
name: 'admin::hasPermissions',
validator: validateHasPermissionsInput,
handler(ctx, config) {
const { actions } = config;
const permissions = actions.map(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) {
return true;
}
if (!isAuthenticated || !ability) {
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';
module.exports = ctx => {
return Boolean(ctx.state.isAuthenticated);
module.exports = policyCtx => {
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', () => {
const ctx = { params: { model: 'foo' } };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi });
const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(true);
});
@ -37,21 +37,21 @@ describe('hasDraftAndPublish policy', () => {
test(`It should fail when the model has draft & publish disabled`, () => {
const ctx = { params: { model: 'bar' } };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi });
const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(false);
});
test(`It should fail when the model doesn't exists`, () => {
const ctx = { params: { model: 'foobar' } };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi });
const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(false);
});
test(`It should fail when params.model isn't provided`, () => {
const ctx = { params: {} };
const res = hasDraftAndPublish(ctx, { strapi: global.strapi });
const res = hasDraftAndPublish(ctx, {}, { strapi: global.strapi });
expect(res).toBe(false);
});
});

View File

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

View File

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

View File

@ -82,7 +82,8 @@ module.exports = strapi => {
router[method](path, routeHandler);
} 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';
const { propOr } = require('lodash/fp');
const policy = require('@strapi/utils/lib/policy');
const { ForbiddenError } = require('@strapi/utils').errors;
const { policy: policyUtils } = require('@strapi/utils');
const getPoliciesConfig = propOr([], 'config.policies');
const resolvePolicies = route => {
const { pluginName, apiName } = route.info || {};
const policiesConfig = getPoliciesConfig(route);
const resolvedPolicies = policiesConfig.map(policyConfig =>
policy.get(policyConfig, { pluginName, apiName })
);
const resolvedPolicies = policyUtils.resolve(policiesConfig, route.info);
const policiesMiddleware = async (ctx, next) => {
const context = policy.createPolicyContext('koa', ctx);
const context = policyUtils.createPolicyContext('koa', ctx);
for (const resolvedPolicy of resolvedPolicies) {
const result = await resolvedPolicy(context, { strapi });
for (const { handler, config } of resolvedPolicies) {
const result = await handler(context, config, { strapi });
if (![true, undefined].includes(result)) {
throw new ForbiddenError('Policies failed.');

View File

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

View File

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

View File

@ -1,25 +1,25 @@
'use strict';
const { getOr } = require('lodash/fp');
const { propOr } = require('lodash/fp');
const { policy: policyUtils } = require('@strapi/utils');
const { ForbiddenError } = require('@strapi/utils').errors;
const getPoliciesConfig = propOr([], 'policies');
const createPoliciesMiddleware = (resolverConfig, { strapi }) => {
const resolverPolicies = getPoliciesConfig(resolverConfig);
const policies = policyUtils.resolve(resolverPolicies);
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
const context = createGraphQLPolicyContext(...rest);
// Run policies & throw an error if one of them fails
for (const policy of policies) {
const result = await policy(context, { strapi });
for (const { handler, config } of policies) {
const result = await handler(context, config, { strapi });
if (![true, undefined].includes(result)) {
throw new ForbiddenError();
throw new ForbiddenError('Policies failed.');
}
}