@plumier/core
Version:
Delightful Node.js Rest Framework
231 lines (230 loc) • 11.6 kB
JavaScript
;
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;