UNPKG

apigeelint

Version:

Node module and tool to lint a bundle for an Apigee API Proxy or sharedflow.

199 lines (178 loc) 6.5 kB
/* Copyright © 2019-2020,2026 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at https://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ const ruleId = require("../lintUtil.js").getRuleId(), debug = require("debug")("apigeelint:" + ruleId); const plugin = { ruleId, name: "Unused Variables", message: "Look for variables that were defined but not referenced.", fatal: false, severity: 1, //warn nodeType: "Bundle", enabled: false, }; let hadWarnErr = false; // This plugin does **NOT** check for variables that are used incorrectly. // TODO: FaultRules // basic regex for finding variables // TODO: handle objects -- right now just verify the highest level variable. const varFinder = /{([^}\.]+?)(\.[^}]+?)??}/g; // preload the symbol table with framework globals var globalSymtab = ["proxy", "request", "response", "message"]; // global bundle ... until I objectize this... // TODO Objectize. var glBundle; var usageMetrics = {}; // Process proxyendpoints in the following order: // 1. preflow - request [steps] // 2. flow - request [steps] // 3. postflow - request [steps] // 4. routerules // - targetendpoint // . pre/flow/post // . pre - response // . flow - response // . post - response // TODO: inspect things referenced by steps to populate symbol table and check for bad var references. const onBundle = function (bundle, cb) { function processFlow(flow, localSymtab) { if (flow.getFlowRequest()) { evaluateSteps(flow.getFlowRequest().getSteps(), localSymtab); } if (flow.getFlowResponse()) { evaluateSteps(flow.getFlowResponse().getSteps(), localSymtab); } } function process(pt, localSymtab) { if (pt.getPreFlow()) { processFlow(pt.getPreFlow(), localSymtab); } pt.getFlows().forEach(function (flow) { processFlow(flow, localSymtab); }); if (pt.getPostFlow()) { processFlow(pt.getPostFlow()); } } glBundle = bundle; bundle.getProxyEndpoints().forEach(function (endpoint) { var localSymtab = []; process(endpoint, localSymtab); // TODO: evalute routrule conditions var intermediateSymtab = localSymtab.slice(0); // don't update orig array references! var intermediateUsage = JSON.parse(JSON.stringify(usageMetrics)); // save usage metrics // iterate through routerules now ... if (bundle.getTargetEndpoints()) { bundle.getTargetEndpoints().forEach(function (target) { localSymtab = intermediateSymtab.slice(0); // reset the local symbol table usageMetrics = JSON.parse(JSON.stringify(intermediateUsage)); // reset usage for this iteration process(target, localSymtab); localSymtab.forEach(function (symbol) { if (!usageMetrics[symbol]) { var result = { ruleId: plugin.ruleId, severity: plugin.severity, source: target.getSource(), line: target.getElement().lineNumber, column: target.getElement().columnNumber, nodeType: plugin.nodeType, message: "Target flow defines but does not use symbol '" + symbol + "'.", }; target.addMessage(result); hadWarnErr = true; } }); }); } }); if (typeof cb == "function") { cb(null, hadWarnErr); } }; // TODO: evaluate where symbols are added, and record them in our symbol table. const evaluateSteps = function (steps, localSymtab) { steps.forEach(function (step) { if (step.getName()) { analyzeVariables(step.getName(), localSymtab).forEach(function (badvar) { const result = { ruleId: plugin.ruleId, severity: 2, //override to error source: step.getSource(), line: step.getElement().lineNumber, column: step.getElement().columnNumber, nodeType: plugin.nodeType, message: `Variable {${badvar}} was used in step name, but not previously defined.`, }; step.addMessage(result); hadWarnErr = true; }); } if (step.getCondition()) { analyzeVariables(step.getCondition().getExpression()).forEach( function (badvar) { const result = { ruleId: plugin.ruleId, severity: plugin.severity, source: step.getSource(), line: step.getElement().lineNumber, column: step.getElement().columnNumber, nodeType: plugin.nodeType, message: `Variable {${badvar}} was used in step condition, but not previously defined.`, }; step.addMessage(result); hadWarnErr = true; }, ); } // TODO: dispatch to handlers to evaluate the assignment and use of variables. const policy = glBundle.getPolicyByName(step.getName()); policy && handlers[policy.getType()] && handlers[policy.getType()](policy, localSymtab); policy && !handlers[policy.getType()] && debug("No handler for policy type '" + policy.getType() + "'"); }); }; // return a list of variables referenced in this string // that are undefined (may be empty) const analyzeVariables = function (value, localSymtab) { const symtab = globalSymtab.concat(localSymtab); const undefinedVars = []; let match; while ((match = varFinder.exec(value)) != null) { const variable = match[1]; usageMetrics[variable] = (usageMetrics[variable] || 0) + 1; if (!(variable in symtab)) { undefinedVars.push(variable); } } return undefinedVars; }; const _identity = function (policy, localSymtab) {}; const handlers = { // TODO: lots more handlers! ExtractVariables(policy, localSymtab) { const payloadVariablesXpath = "/ExtractVariables/*[self::JSONPayload or self::XMLPayload]/Variable/@name"; policy .select(payloadVariablesXpath) .forEach((variableDecl) => localSymtab.push(variableDecl.value)); }, JSONThreatProtection: _identity, }; module.exports = { plugin, onBundle, };