UNPKG

@plumier/core

Version:

Delightful Node.js Rest Framework

231 lines (230 loc) • 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getPolicyInfo = exports.createAuthorizationAnalyzer = exports.analyzeAuthPolicyNameConflict = exports.updateRouteAuthorizationAccess = void 0; const reflect_1 = require("@plumier/reflect"); const authorization_1 = require("./authorization"); const common_1 = require("./common"); // --------------------------------------------------------------------- // // ------------------------------ HELPERS ------------------------------ // // --------------------------------------------------------------------- // function getEntityProvider(route) { const entityProvider = route.action.decorators.find((x) => x.kind === "plumier-meta:entity-policy-provider"); return entityProvider === null || entityProvider === void 0 ? void 0 : entityProvider.entity; } function getPolicyInfo(authDecorators, registeredPolicy) { const getType = (name) => { const policies = registeredPolicy.filter(x => x.name === name); const result = []; for (const pol of policies) { if (pol instanceof authorization_1.EntityAuthPolicy) result.push({ type: "EntityPolicy", entity: pol.entity }); else result.push({ type: "AuthPolicy" }); } return result.length === 0 ? [{ type: "Mistyped" }] : result; }; const result = []; for (const item of authDecorators) { for (const pol of item.policies) { const types = getType(pol); result.push(...types.map(x => (Object.assign(Object.assign({}, x), { name: pol, access: item.access, decoratorTarget: item.appliedClass })))); } } return result; } exports.getPolicyInfo = getPolicyInfo; function getMistypedMultiple(params, registeredPolicy, predicate) { const messages = []; for (const par of params) { const decs = par.decorators.filter((x) => x.type === "plumier-meta:authorize"); const mistyped = getPolicyInfo(decs, registeredPolicy) .filter(predicate) .map(x => x.name); if (mistyped.length > 0) messages.push({ name: par.name, mistyped: mistyped.join(", ") }); } return messages; } // --------------------------------------------------------------------- // // ------------------------ MISTYPED AUTH POLICY ----------------------- // // --------------------------------------------------------------------- // function checkMistypedPoliciesOnRoute(route, policies, globalAuthorize) { const authDecorators = (0, authorization_1.getRouteAuthorizeDecorators)(route, globalAuthorize); const mistyped = getPolicyInfo(authDecorators, policies) .filter(x => x.type === "Mistyped") .map(x => x.name); return mistyped.length > 0 ? [{ type: "error", message: `Route uses unknown authorization policy ${mistyped.join(", ")}` }] : []; } function checkMistypedPoliciesOnParameters(route, policies, globalAuthorize) { const mistyped = getMistypedMultiple(route.action.parameters, policies, x => x.type === "Mistyped"); return mistyped.map(x => ({ type: "error", message: `Parameter ${x.name} uses unknown authorization policy ${x.mistyped}` })); } function checkMistypePoliciesOnModel(route, policies, globalAuthorize) { const messages = []; for (const par of route.action.parameters) { const type = Array.isArray(par.type) ? par.type[0] : par.type; if ((0, common_1.isCustomClass)(type)) { const meta = (0, reflect_1.reflect)(type); const mistyped = getMistypedMultiple(meta.properties, policies, x => x.type === "Mistyped"); messages.push(...mistyped.map(x => ({ type: "error", message: `Model property ${type.name}.${x.name} uses unknown authorization policy ${x.mistyped}` }))); } } return messages; } function checkMistypePoliciesOnReturnValue(route, policies, globalAuthorize) { const type = Array.isArray(route.action.returnType) ? route.action.returnType[0] : route.action.returnType; if ((0, common_1.isCustomClass)(type)) { const meta = (0, reflect_1.reflect)(type); const mistyped = getMistypedMultiple(meta.properties, policies, x => x.type === "Mistyped"); return mistyped.map(x => ({ type: "error", message: `Return type property ${type.name}.${x.name} uses unknown authorization policy ${x.mistyped}` })); } return []; } // --------------------------------------------------------------------- // // ------------------- APPLIED ON NON ENTITY PROVIDER ------------------ // // --------------------------------------------------------------------- // function missingEntityProviderMessage(policies, prefix) { const names = Array.from(new Set(policies)).join(", "); const pref = prefix ? ` on ${prefix}` : ""; return { type: "error", message: `Entity policy ${names}${pref} used on non entity provider route` }; } function checkMissingEntityProviderOnRoute(route, policies, globalAuthorize) { const authDecorators = (0, authorization_1.getRouteAuthorizeDecorators)(route, globalAuthorize); const provider = getEntityProvider(route); if (!!provider) return []; const infos = getPolicyInfo(authDecorators, policies) .filter(x => x.type === "EntityPolicy" && x.access === "route") .map(x => x.name); return infos.length > 0 ? [missingEntityProviderMessage(infos)] : []; } function checkMissingEntityProviderOnParameters(route, policies, globalAuthorize) { const provider = getEntityProvider(route); if (!!provider) return []; const result = []; for (const par of route.action.parameters) { const decs = par.decorators.filter((x) => x.type === "plumier-meta:authorize"); const infos = getPolicyInfo(decs, policies).filter(x => x.type === "EntityPolicy" && x.access === "write").map(x => x.name); if (infos.length > 0) result.push(missingEntityProviderMessage(infos, `parameter ${par.name}`)); } return result; } function checkMissingEntityProviderOnModel(route, policies, globalAuthorize) { const provider = getEntityProvider(route); if (!!provider) return []; const result = []; for (const par of route.action.parameters) { const type = Array.isArray(par.type) ? par.type[0] : par.type; if ((0, common_1.isCustomClass)(type)) { const meta = (0, reflect_1.reflect)(type); for (const prop of meta.properties) { const decs = prop.decorators.filter((x) => x.type === "plumier-meta:authorize"); const infos = getPolicyInfo(decs, policies).filter(x => x.type === "EntityPolicy" && x.access === "write").map(x => x.name); if (infos.length > 0) result.push(missingEntityProviderMessage(infos, `model ${type.name}.${prop.name}`)); } } } return result; } // --------------------------------------------------------------------- // // ----------------------- MISSING ENTITY POLICY ----------------------- // // --------------------------------------------------------------------- // function missingEntityPolicyErrorMessage(policies, provider, location) { const loc = location ? ` on ${location}` : ""; const names = Array.from(new Set(policies.map(x => x.name))); return { type: "error", message: `Entity policy ${names.join(", ")} for entity ${provider.name} ${names.length === 1 ? "is" : "are"} not found${loc}` }; } function checkMissingEntityPolicyOnRoute(route, policies, globalAuthorize) { const authDecorators = (0, authorization_1.getRouteAuthorizeDecorators)(route, globalAuthorize); const provider = getEntityProvider(route); if (!provider) return []; const infos = getPolicyInfo(authDecorators, policies) .filter(x => x.type === "EntityPolicy" && x.access === "route"); return infos.length > 0 && infos.every(x => x.entity !== provider) ? [missingEntityPolicyErrorMessage(infos, provider)] : []; } function checkMissingEntityPolicyOnParameters(route, policies, globalAuthorize) { const provider = getEntityProvider(route); if (!provider) return []; const result = []; for (const par of route.action.parameters) { const decs = par.decorators.filter((x) => x.type === "plumier-meta:authorize"); const infos = getPolicyInfo(decs, policies).filter(x => x.type === "EntityPolicy" && x.access === "write"); if (infos.length > 0 && infos.every(x => x.entity !== provider)) result.push(missingEntityPolicyErrorMessage(infos, provider, `parameter ${par.name}`)); } return result; } function checkMissingEntityPolicyOnModel(route, policies, globalAuthorize) { const provider = getEntityProvider(route); if (!provider) return []; const result = []; for (const par of route.action.parameters) { const type = Array.isArray(par.type) ? par.type[0] : par.type; if ((0, common_1.isCustomClass)(type)) { const meta = (0, reflect_1.reflect)(type); for (const prop of meta.properties) { const decs = prop.decorators.filter((x) => x.type === "plumier-meta:authorize"); const infos = getPolicyInfo(decs, policies).filter(x => x.type === "EntityPolicy" && x.access === "write"); if (infos.length > 0 && infos.every(x => x.entity !== provider)) result.push(missingEntityPolicyErrorMessage(infos, provider, `model property ${type.name}.${prop.name}`)); } } } return result; } // --------------------------------------------------------------------- // // ----------------------------- ANALYZERS ----------------------------- // // --------------------------------------------------------------------- // function updateRouteAuthorizationAccess(routes, config) { routes.forEach(x => { if (x.kind === "ActionRoute") { const decorators = (0, authorization_1.getRouteAuthorizeDecorators)(x, config.globalAuthorizations); x.access = decorators.map(x => x.tag).join("|"); } }); } exports.updateRouteAuthorizationAccess = updateRouteAuthorizationAccess; function analyzeAuthPolicyNameConflict(policies) { for (let i = 0; i < policies.length - 1; i++) { const policy = new policies[i](); const nextI = i + 1; for (let j = nextI; j < policies.length; j++) { const next = new policies[j](); if (policy.conflict(next)) throw new Error(`There are more than one authorization policies named ${policy.name}`); } } } exports.analyzeAuthPolicyNameConflict = analyzeAuthPolicyNameConflict; function createAuthorizationAnalyzer(policies, globalAuthorize) { const analyzers = [ // auth policy checkMistypedPoliciesOnRoute, checkMistypedPoliciesOnParameters, checkMistypePoliciesOnModel, checkMistypePoliciesOnReturnValue, // missing entity provider checkMissingEntityProviderOnRoute, checkMissingEntityProviderOnParameters, checkMissingEntityProviderOnModel, // missing entity policy checkMissingEntityPolicyOnRoute, checkMissingEntityPolicyOnParameters, checkMissingEntityPolicyOnModel, ]; return analyzers.map(analyser => (info) => info.kind === "VirtualRoute" ? [] : analyser(info, policies, globalAuthorize)); } exports.createAuthorizationAnalyzer = createAuthorizationAnalyzer;