diff --git a/README.md b/README.md index ff5add81..65bfbf71 100644 --- a/README.md +++ b/README.md @@ -81,9 +81,14 @@ This example uses the "externalPlugins" directory with a plugin for alternate po ### Listing plugins List plugins and formatters, with or without --externalPluginsDirectory. -``` +```sh apigeelint --list -apigeelint --list -x ./externalPlugins or apigeelint --list --externalPluginsDirectory ./externalPlugins +apigeelint --list -x ./externalPlugins + +# or + +apigeelint --list --externalPluginsDirectory ./externalPlugins + ``` ## Does this tool just lint or does it also check style? @@ -122,7 +127,7 @@ You can also contribute by reporting issues, asking for new features. ## Rules The list of rules is a work in progress. We expect it to increase over time. As -product features change (new policies, etc), we will change rules as +product features change (new policies, deprecated policies, etc), we will change rules as well. This is the current list: @@ -141,32 +146,32 @@ This is the current list: |   |:white_check_mark:| BN009 | Statistics Collector - duplicate policies | Warn on duplicate policies when no conditions are present or conditions are duplicates. | |   |:white_check_mark:| BN010 | Missing policies | Issue an error if a referenced policy is not present in the bundle. | | Proxy Definition |   |   |   |   | -|   |:white_check_mark:| PD001 | RouteRules to Targets | RouteRules should map to defined Targets | -|   |:white_check_mark:| PD002 | Unreachable Route Rules - defaults | Only one RouteRule should be present without a condition | +|   |:white_check_mark:| PD001 | RouteRules to Targets | RouteRules should map to defined Targets. | +|   |:white_check_mark:| PD002 | Unreachable Route Rules - defaults | Only one RouteRule should be present without a condition. | |   |:white_check_mark:| PD003 | Unreachable Route Rules | RouteRule without a condition should be last. | |   |:white_check_mark:| PD004 | ProxyEndpoint name | ProxyEndpoint name should match basename of filename. | |   |:white_check_mark:| PD005 | VirtualHost | ProxyEndpoint should have one HTTPProxyConnection, and in the case of profile=apigeex, no VirtualHost. | | Target Definition |   |   |   |   | |   |:white_check_mark:| TD001 | Mgmt Server as Target | Discourage calls to the Management Server from a Proxy via target. | -|   |:white_check_mark:| TD002 | Use Target Servers | Encourage the use of target servers | +|   |:white_check_mark:| TD002 | Use Target Servers | Encourage the use of target servers. | |   |:white_check_mark:| TD003 | TargetEndpoint name | TargetEndpoint name should match basename of filename. | |   |:white_check_mark:| TD004 | TargetEndpoint SSLInfo | TargetEndpoint HTTPTargetConnection should enable TLS/SSL. | |   |:white_check_mark:| TD005 | TargetEndpoint SSLInfo references | TargetEndpoint SSLInfo should use references for KeyStore and TrustStore. | | Flow |   |   |   |   | |   |:white_check_mark:| FL001 | Unconditional Flows | Only one unconditional flow will get executed. Error if more than one was detected. | | Step |   |   |   |   | -|   |:white_check_mark:| ST001 | Empty Step | Empty steps clutter the bundle. | +|   |:white_check_mark:| ST001 | Empty Step | Empty steps clutter the bundle. | |   |:white_check_mark:| ST002 | Step Structure | each Step should have at most one Name element, one Condition element, no others. | +|   |:white_check_mark:| ST003 | Extract Variables Step with JSONPayload | A check for message content should be performed before policy execution. | +|   |:white_check_mark:| ST004 | Extract Variables Step with XMLPayload | A check for message content should be performed before policy execution. | +|   |:white_check_mark:| ST005 | Extract Variables Step with FormParam | A check for message content should be performed before policy execution. | +|   |:white_check_mark:| ST006 | JSON Threat Protection Step | A check for message content should be performed before policy execution. | +|   |:white_check_mark:| ST007 | XML Threat Protection Step | A check for message content should be performed before policy execution. | | Policy |   |   |   |   | -|   |:white_check_mark:| PO001 | JSON Threat Protection | A check for a body element must be performed before policy execution. | -|   |:white_check_mark:| PO002 | XML Threat Protection | A check for a body element must be performed before policy execution. | -|   |:white_check_mark:| PO003 | Extract Variables with JSONPayload | A check for a body element must be performed before policy execution. | -|   |:white_check_mark:| PO004 | Extract Variables with XMLPayload | A check for a body element must be performed before policy execution. | -|   |:white_check_mark:| PO005 | Extract Variables with FormParam | A check for a body element must be performed before policy execution. | |   |:white_check_mark:| PO006 | Policy Name & filename agreement | Policy name attribute should coincide with the policy filename. | |   |:white_check_mark:| PO007 | Policy Naming Conventions - type indication | It is recommended that the policy name use a prefix or follow a pattern that indicates the policy type. | |   |:white_check_mark:| PO008 | Policy DisplayName & DisplayName agreement | Check that the policy filename matches the display name of the policy. | -|   |:white_medium_square:| PO009 | Service Callout Target - Mgmt Server | Targeting management server may result in higher than expected latency use with caution. | +|   |:white_medium_square:| PO009 | Service Callout Target - Mgmt Server | Targeting management server may result in higher than expected latency; use with caution. | |   |:white_medium_square:| PO010 | Service Callout Target - Target Server | Encourage use of target servers. | |   |:white_medium_square:| PO011 | Service Callout Target - Dynamic URLs | Error on dynamic URLs in target server URL tag. | |   |:white_check_mark:| PO012 | AssignMessage/AssignTo | Warn on unnecessary AssignTo in AssignMessage when createNew is false and no destination variable. | @@ -210,6 +215,28 @@ This is the current list: From an implementation perspective, the focus is on plugin support and flexibility over performance. Compute is cheap. +## Release Notes + +### Release v2.31.0 + +#### Condition checks around policies + +In release v2.31.0, the plugins PO001, PO002, PO003, PO004, and PO005 have been +converted to ST006, ST007, ST003, ST004, and ST005, respectively. These plugins +move from the "Policy" category to the "Step" category because the plugin +analyzes the attachment of the policy in a Step element, rather than the policy +itself. Also these plugins will now generate warnings, rather than errors. + +If previously you excluded PO003 via the `--excluded` option, you must now +exclude ST003, and so on. + +#### Sharedflows + +Starting with release v2.31.0, using apigeelint against Sharedflows will +generate a correct report. Previously the report on a sharedflow was truncated +and omitted some warnings and errors. + + ## Support diff --git a/lib/package/Bundle.js b/lib/package/Bundle.js index 98a4dc27..10c1bf16 100644 --- a/lib/package/Bundle.js +++ b/lib/package/Bundle.js @@ -306,19 +306,13 @@ function Bundle(config, cb) { Bundle.prototype.getReport = function(cb) { //go out and getReport from endpoints, resources, policies - var result = [this.report], - append = function(a) { - a.forEach(function(e) { - result.push(e.getReport()); - }); - }; + let result = [this.report]; + const appendCollection = + collection => collection.forEach( item => result.push(item.getReport()) ); - //no endpoints in shared flow - if(this.bundleTypeName !== bundleType.BundleType.SHAREDFLOW){ - append(this.getEndpoints()); - } - append(this.getPolicies()); - append(this.getResources()); + appendCollection(this.getEndpoints()); // for either apiproxy or sharedflow + appendCollection(this.getPolicies()); + appendCollection(this.getResources()); if (cb) { cb(result); } @@ -326,9 +320,6 @@ Bundle.prototype.getReport = function(cb) { }; Bundle.prototype.addMessage = function(msg) { - //lets inspect what we got - //if it includes a plugin field - //then process new style if (msg.hasOwnProperty("plugin")) { msg.ruleId = msg.plugin.ruleId; if (!msg.severity) msg.severity = msg.plugin.severity; @@ -563,7 +554,7 @@ Bundle.prototype.getDefaultFaultRules = function() { }; Bundle.prototype.summarize = function() { - var summary = { + let summary = { report: this.getReport(), root: this.root, name: this.getName(), diff --git a/lib/package/Endpoint.js b/lib/package/Endpoint.js index 3ff35f90..ef429002 100644 --- a/lib/package/Endpoint.js +++ b/lib/package/Endpoint.js @@ -31,7 +31,7 @@ function Endpoint(element, parent, fname, bundletype) { debug(`> new Endpoint() ${fname}`); this.bundleType = bundletype ? bundletype : "apiproxy" //default to apiproxy if bundletype not passed this.fileName = fname; - this.parent = parent; + this.parent = parent; // the bundle this.element = element; this.report = { filePath: fname.substring(fname.indexOf("/" + this.bundleType)), @@ -169,7 +169,6 @@ Endpoint.prototype.getPostClientFlow = function() { Endpoint.prototype.getSharedFlow = function() { if (!this.sharedFlow) { - //find the preflow tag var doc = xpath.select(".", this.element); if (doc && doc[0]) { this.sharedFlow = new Flow(doc[0], this); @@ -230,7 +229,9 @@ Endpoint.prototype.getFlows = function() { else { debug(`Flow`); } - flows.push(new Flow(elt, ep)); + let f = new Flow(elt, ep); + debug(`getFlows() flow= ${f.element.tagName}`); + flows.push(f); }); }); } @@ -428,6 +429,7 @@ Endpoint.prototype.getParent = function() { }; Endpoint.prototype.addMessage = function(msg) { + debug(`> addMessage`); //Severity should be one of the following: 0 = off, 1 = warning, 2 = error if (msg.hasOwnProperty("plugin")) { msg.ruleId = msg.plugin.ruleId; @@ -435,7 +437,7 @@ Endpoint.prototype.addMessage = function(msg) { msg.nodeType = msg.plugin.nodeType; delete msg.plugin; } - debug(`Endpoint.addMessage ${msg.ruleId}: ${msg.message}`); + debug(`addMessage ${msg.ruleId}: ${msg.message}`); if (!msg.hasOwnProperty("entity")) { msg.entity = this; @@ -466,37 +468,42 @@ Endpoint.prototype.addMessage = function(msg) { }; Endpoint.prototype.summarize = function() { - var summary = {}; + debug('> summarize'); + let summary = { + name : this.getName(), + type : this.getType(), + }; + + if(this.bundleType === bundleTypes.BundleType.SHAREDFLOW){ + debug('summarize - is SharedFlow'); + summary.flows = + this.getSharedFlow().summarize(); + } + else { + debug('summarize - is Apiproxy'); + summary.proxyName = this.getProxyName(); + + let faultRules = this.getFaultRules(); + if (faultRules) { + summary.faultRules = []; + faultRules.forEach(function(fr) { + summary.faultRules.push(fr.summarize()); + }); + } - summary.name = this.getName(); - summary.type = this.getType(); - summary.proxyName = this.getProxyName(); + summary.defaultFaultRule = + (this.getDefaultFaultRule() && this.getDefaultFaultRule().summarize()) || + {}; - var faultRules = this.getFaultRules(); - if (faultRules) { - summary.faultRules = []; - faultRules.forEach(function(fr) { - summary.faultRules.push(fr.summarize()); - }); - } + summary.preFlow = (this.getPreFlow() && this.getPreFlow().summarize()) || {}; - summary.defaultFaultRule = - (this.getDefaultFaultRule() && this.getDefaultFaultRule().summarize()) || - {}; + summary.flows = this.getFlows().map(flow => flow.summarize()); - summary.preFlow = (this.getPreFlow() && this.getPreFlow().summarize()) || {}; - - summary.flows = []; - this.getFlows().forEach(function(flow) { - summary.flows.push(flow.summarize()); - }); - summary.postFlow = - (this.getPostFlow() && this.getPostFlow().summarize()) || {}; - summary.routeRules = []; - this.getRouteRules().forEach(function(rr) { - summary.routeRules.push(rr.summarize()); - }); + summary.postFlow = + (this.getPostFlow() && this.getPostFlow().summarize()) || {}; + summary.routeRules = this.getRouteRules().map(rr => rr.summarize()); + } return summary; }; diff --git a/lib/package/Flow.js b/lib/package/Flow.js index 140c77b6..ede0ccfe 100644 --- a/lib/package/Flow.js +++ b/lib/package/Flow.js @@ -22,12 +22,14 @@ const xpath = require("xpath"), getcb = myUtil.curry(myUtil.diagcb, debug); function Flow(element, parent) { - this.parent = parent; + this.parent = parent; // like ProxyEndpoint , TargetEndpoint this.element = element; this.condition = null; this.flowResponse = null; this.sharedflow = null; this.description = null; + let self = this; + debug(`Flow ctor: self: ${self.element.tagName}`); } Flow.prototype.getName = function() { @@ -35,6 +37,7 @@ Flow.prototype.getName = function() { var attr = xpath.select("./@name", this.element); this.name = (attr[0] && attr[0].value) || ""; } + debug(`getName(): self: ${self.element.tagName}, name: ${this.name}`); return this.name; }; @@ -43,9 +46,10 @@ Flow.prototype.getMessages = function() { }; Flow.prototype.getType = function() { - //lets go ahead and look this up if (!this.type) { - this.type = xpath.select("name(/*)", this.element); + // Flow | PreFlow || PostFlow || PostClientFlow + //this.type = xpath.select("name(/*)", this.element); + this.type = this.element.tagName; } return this.type; }; @@ -93,7 +97,8 @@ Flow.prototype.getFlowRequest = function() { }; Flow.prototype.getFlowResponse = function() { - if (this.flowResponse == null) { + let self = this; + if ( ! this.flowResponse) { let doc = xpath.select("./Response", this.element); this.flowResponse = (doc && doc[0]) ? new FlowPhase(doc[0], this) : false; } diff --git a/lib/package/FlowPhase.js b/lib/package/FlowPhase.js index 11979618..4c0ff60f 100644 --- a/lib/package/FlowPhase.js +++ b/lib/package/FlowPhase.js @@ -23,6 +23,7 @@ const xpath = require("xpath"), function FlowPhase(element, parent) { this.parent = parent; this.element = element; + debug(`parent is ${parent.getType()}`); } FlowPhase.prototype.getType = function() { @@ -62,7 +63,7 @@ FlowPhase.prototype.onSteps = function(pluginFunction, cb) { let steps = this.getSteps(); if (steps && steps.length > 0) { steps.forEach( (step, ix) => - pluginFunction(step, getcb(`step ${ix}}`))); + pluginFunction(step, getcb(`step ${ix}`))); } cb(null, {}); }; @@ -71,7 +72,7 @@ FlowPhase.prototype.onConditions = function(pluginFunction, cb) { let steps = this.getSteps(); if (steps && steps.length > 0) { steps.forEach( (step, ix) => - step.onConditions(pluginFunction, getcb(`step ${ix}}`))); + step.onConditions(pluginFunction, getcb(`step ${ix}`))); } cb(null, {}); }; diff --git a/lib/package/plugins/EP002-misplacedElements.js b/lib/package/plugins/EP002-misplacedElements.js index b99c416c..188e4bd9 100644 --- a/lib/package/plugins/EP002-misplacedElements.js +++ b/lib/package/plugins/EP002-misplacedElements.js @@ -39,7 +39,7 @@ const onBundle = function(bundle, cb) { const onProxyEndpoint = function(endpoint, cb) { debug('onProxyEndpoint'); - let checker = new EndpointChecker(endpoint, true); + let checker = new EndpointChecker(endpoint); let flagged = checker.check(); if (typeof(cb) == 'function') { cb(null, flagged); @@ -48,7 +48,7 @@ const onProxyEndpoint = function(endpoint, cb) { const onTargetEndpoint = function(endpoint, cb) { debug('onTargetEndpoint'); - let checker = new EndpointChecker(endpoint, false); + let checker = new EndpointChecker(endpoint); let flagged = checker.check(); if (typeof(cb) == 'function') { cb(null, flagged); @@ -71,7 +71,7 @@ const _markEndpoint = (endpoint, message, line, column) => { }; const allowedParents = { - Step: ['Request', 'Response', 'FaultRule', 'DefaultFaultRule'], + Step: ['Request', 'Response', 'FaultRule', 'DefaultFaultRule', 'SharedFlow'], Request: ['PreFlow', 'PostFlow', 'Flow', 'PostClientFlow', 'HTTPMonitor'], Response: ['PreFlow', 'PostFlow', 'Flow', 'PostClientFlow'], Flows: ['ProxyEndpoint', 'TargetEndpoint'], @@ -105,14 +105,15 @@ const allowedChildren = { 'Description', 'FaultRules', 'DefaultFaultRule', 'HTTPProxyConnection'], TargetEndpoint: ['PreFlow', 'PostFlow', 'Flows', 'Description', 'FaultRules', 'DefaultFaultRule', 'HostedTarget', - 'LocalTargetConnection', 'HTTPTargetConnection'] + 'LocalTargetConnection', 'HTTPTargetConnection'], + SharedFlow: ['Step' ] }; class EndpointChecker { - constructor(endpoint, isProxyEndpoint) { - debug('EndpointChecker ctor (%s)', endpoint.getName()); + constructor(endpoint) { + debug('EndpointChecker ctor (%s) (%s)', endpoint.getName(), endpoint.getType()); this.endpoint = endpoint; - this.isProxyEndpoint = isProxyEndpoint; + this.endpointType = endpoint.getType(); // ProxyEndpoint, TargetEndpoint, SharedFlow this.flagged = false; } @@ -120,7 +121,7 @@ class EndpointChecker { try { const self = this; - let ep = self.isProxyEndpoint ? 'ProxyEndpoint': 'TargetEndpoint'; + let ep = self.endpointType; let topLevelChildren = xpath.select("*", self.endpoint.element); topLevelChildren.forEach(child => { if (allowedChildren[ep].indexOf(child.tagName)< 0) { @@ -169,7 +170,7 @@ class EndpointChecker { }); // exactly one of LocalTarget and HTTPTarget is required - if (! self.isProxyEndpoint) { + if (ep == 'TargetEndpoint') { let condition = []; if(bundleProfile != "apigee") condition = ['LocalTargetConnection', 'HTTPTargetConnection'].map(n => `self::${n}`).join(' or '); @@ -193,7 +194,7 @@ class EndpointChecker { } // in HealthMonitor, exactly one of HTTPMonitor or TCPMonitor is required - if (! self.isProxyEndpoint) { + if (ep == 'TargetEndpoint') { let healthMonitors = xpath.select(`HTTPTargetConnection/HealthMonitor`, self.endpoint.element); if (healthMonitors.length > 1) { diff --git a/lib/package/plugins/PD005-unnecessaryVirtualHost.js b/lib/package/plugins/PD005-unnecessaryVirtualHost.js index 258c4a58..bdfcdf57 100644 --- a/lib/package/plugins/PD005-unnecessaryVirtualHost.js +++ b/lib/package/plugins/PD005-unnecessaryVirtualHost.js @@ -38,46 +38,48 @@ const onBundle = function(bundle, cb) { const onProxyEndpoint = function(ep, cb) { - let connections = ep.select('/ProxyEndpoint/HTTPProxyConnection'), - flagged = false; + if (ep.getType() == 'ProxyEndpoint') { // exclude SharedFlows + let connections = ep.select('/ProxyEndpoint/HTTPProxyConnection'), + flagged = false; - debug(`found ${connections.length} HTTPProxyConnection elements`); - if (connections.length) { - if (connections.length > 1) { + debug(`found ${connections.length} HTTPProxyConnection elements`); + if (connections.length) { + if (connections.length > 1) { + flagged = true; + connections.slice(1) + .forEach( c => + ep.addMessage({ + plugin, + line: c.lineNumber, + column: c.columnNumber, + message: `Multiple HTTPProxyConnection elements.` + })); + } + else if (profile == 'apigeex') { + // there is exactly 1 element, check for VirtualHost if apigeex + ep.select('/ProxyEndpoint/HTTPProxyConnection/VirtualHost') + .forEach(vhost => { + ep.addMessage({ + plugin, + line: vhost.lineNumber, + column: vhost.columnNumber, + message: `Unnecessary VirtualHost element.` + }); + flagged = true; + }); + } + } + else { + ep.addMessage({ + plugin, + message: `Missing HTTPProxyConnection.` + }); flagged = true; - connections.slice(1) - .forEach( c => - ep.addMessage({ - plugin, - line: c.lineNumber, - column: c.columnNumber, - message: `Multiple HTTPProxyConnection elements.` - })); } - else if (profile == 'apigeex') { - // there is exactly 1 element, check for VirtualHost if apigeex - ep.select('/ProxyEndpoint/HTTPProxyConnection/VirtualHost') - .forEach(vhost => { - ep.addMessage({ - plugin, - line: vhost.lineNumber, - column: vhost.columnNumber, - message: `Unnecessary VirtualHost element.` - }); - flagged = true; - }); + if (typeof(cb) == 'function') { + cb(null, flagged); } } - else { - ep.addMessage({ - plugin, - message: `Missing HTTPProxyConnection.` - }); - flagged = true; - } - if (typeof(cb) == 'function') { - cb(null, flagged); - } }; module.exports = { diff --git a/lib/package/plugins/PO001-threatProtectionJSONConditionCheck.js b/lib/package/plugins/PO001-threatProtectionJSONConditionCheck.js deleted file mode 100644 index dcef6e57..00000000 --- a/lib/package/plugins/PO001-threatProtectionJSONConditionCheck.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright 2019-2021 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. -*/ - -//|   |:white_medium_square:| PO001 | JSON Threat Protection | A check for a body element must be performed before policy execution. | - - -const ruleId = require("../myUtil.js").getRuleId(), - debug = require("debug")("apigeelint:" + ruleId); - -const plugin = { - ruleId, - name: "JSONThreatProtection check for body", - message: - "A check for a body element must be performed before policy execution.", - fatal: false, - severity: 2, //error - nodeType: "JSONThreatProtection", - enabled: true - }, - condRegExp = - "(response.content|response.formstring|request.content|request.formstring|message.content|message.formstring|request.verb|message.verb)"; - -const checker = require('./_policyConditionCheck.js'); - -const onPolicy = checker(plugin, "JSONThreatProtection", condRegExp, debug); - -module.exports = { - plugin, - onPolicy -}; diff --git a/lib/package/plugins/PO002-threatProtectionXMLConditionCheck.js b/lib/package/plugins/PO002-threatProtectionXMLConditionCheck.js deleted file mode 100644 index fd2d38ee..00000000 --- a/lib/package/plugins/PO002-threatProtectionXMLConditionCheck.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - Copyright 2019-2021 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. -*/ - - -//|   |:white_medium_square:| PO002 | XML Threat Protection | A check for a body element must be performed before policy execution. | - -const ruleId = require("../myUtil.js").getRuleId(), - debug = require("debug")("apigeelint:" + ruleId); - -const plugin = { - ruleId, - name: "XMLThreatProtection check for body", - message: - "A check for a body element must be performed before policy execution.", - fatal: false, - severity: 2, //error - nodeType: "XMLThreatProtection", - enabled: true - }, - condRegExp = - "(response.content|response.form|request.content|request.form|message.content|message.form|message.verb|request.verb)"; - -const checker = require('./_policyConditionCheck.js'); - -const onPolicy = checker(plugin, "XMLThreatProtection", condRegExp, debug); - -module.exports = { - plugin, - onPolicy -}; diff --git a/lib/package/plugins/PO005-extractFormParamConditionCheck.js b/lib/package/plugins/PO005-extractFormParamConditionCheck.js deleted file mode 100644 index 99bcf398..00000000 --- a/lib/package/plugins/PO005-extractFormParamConditionCheck.js +++ /dev/null @@ -1,94 +0,0 @@ -/* - Copyright 2019-2020 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. -*/ - -//extractFormParamConditionCheck -//|   |:white_medium_square:| PO005 | Extract Variables with FormParam | A check for a body element must be performed before policy execution. | - -const ruleId = require("../myUtil.js").getRuleId(), - debug = require("debug")("apigeelint:" + ruleId), - xpath = require("xpath"); - -const plugin = { - ruleId, - name: "Extract Variables with FormParam", - message: - "A check for a body element must be performed before policy execution.", - fatal: false, - severity: 2, //error - nodeType: "ExtractVariables", - enabled: true - }; - -const onPolicy = function(policy, cb) { - var condRegExp = - "(response.content|response.form|request.content|request.form|message.content|message.form|message.verb|request.verb)"; - - var hadWarning = false; - if (policy.getType() === "ExtractVariables") { - var formParam = xpath.select( - "/ExtractVariables/FormParam/text()", - policy.getElement() - ); - - var sourceElement = xpath.select( - "/ExtractVariables/Source/text()", - policy.getElement() - ); - - if (sourceElement.length) { - let source = sourceElement[0].data; - - condRegExp = !source.match(condRegExp) ? source : condRegExp; - } - - if (formParam.length > 0 && policy.getSteps().length > 0) { - //check each step for a condition on body - let hasAllBodyChecks = - policy.getSteps().every( step => { - let condition = step.getCondition(); - if (condition && condition.getExpression().match(condRegExp)) { - return true; - } - - if (step.parent && step.parent.getType() === "Flow") { - condition = step.parent.getCondition(); - if (condition && condition.getExpression().match(condRegExp)) { - return true; - } - } - return false; - }); - - if ( ! hasAllBodyChecks) { - hadWarning = true; - policy.addMessage({ - plugin, - message: - "An appropriate check for a message body was not found on the enclosing Step or Flow." - }); - } - } - } - - if (typeof(cb) == 'function') { - cb(null, hadWarning); - } -}; - -module.exports = { - plugin, - onPolicy -}; diff --git a/lib/package/plugins/PO003-extractJSONConditionCheck.js b/lib/package/plugins/ST003-extractJSONConditionCheck.js similarity index 56% rename from lib/package/plugins/PO003-extractJSONConditionCheck.js rename to lib/package/plugins/ST003-extractJSONConditionCheck.js index d09247be..c2d0529f 100644 --- a/lib/package/plugins/PO003-extractJSONConditionCheck.js +++ b/lib/package/plugins/ST003-extractJSONConditionCheck.js @@ -1,5 +1,5 @@ /* - Copyright 2019-2021 Google LLC + Copyright 2019-2022 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,8 +14,8 @@ limitations under the License. */ -//extractJSONConditionCheck -//|   |:white_medium_square:| PO003 | Extract Variables with JSONPayload | A check for a body element must be performed before policy execution. | + +//|   |:white_medium_square:| ST003 | Extract Variables with JSONPayload | A check for a body element must be performed before policy execution. | const ruleId = require("../myUtil.js").getRuleId(), debug = require("debug")("apigeelint:" + ruleId); @@ -26,14 +26,32 @@ const plugin = { message: "A check for a body element must be performed before policy execution.", fatal: false, - severity: 2, //error - nodeType: "ExtractVariables", + severity: 1, //1=warning, 2=error + nodeType: 'Step', enabled: true }; -const checker = require('./_extractVariablesCheck.js').check; +const EVAttachmentChecker = require('./_extractVariablesPayloadCheck.js'); +const checker = new EVAttachmentChecker(plugin, 'JSONPayload', debug); + +const onProxyEndpoint = function(endpoint, cb) { + debug('onProxyEndpoint'); + let flagged = checker.check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +const onTargetEndpoint = function(endpoint, cb) { + debug('onTargetEndpoint'); + let flagged = checker.check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; module.exports = { plugin, - onPolicy : checker(plugin, 'JSONPayload', debug) + onProxyEndpoint, + onTargetEndpoint }; diff --git a/lib/package/plugins/PO004-extractXMLConditionCheck.js b/lib/package/plugins/ST004-extractXMLConditionCheck.js similarity index 56% rename from lib/package/plugins/PO004-extractXMLConditionCheck.js rename to lib/package/plugins/ST004-extractXMLConditionCheck.js index e1a0fa13..8363a840 100644 --- a/lib/package/plugins/PO004-extractXMLConditionCheck.js +++ b/lib/package/plugins/ST004-extractXMLConditionCheck.js @@ -1,5 +1,5 @@ /* - Copyright 2019-2021 Google LLC + Copyright 2019-2022 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,8 +14,7 @@ limitations under the License. */ -//extractXMLConditionCheck -//|   |:white_check_mark:| PO004 | Extract Variables with XMLPayload | A check for a body element must be performed before policy execution. | +//|   |:white_check_mark:| ST004 | Extract Variables with XMLPayload | A check for a body element must be performed before policy execution. | const ruleId = require("../myUtil.js").getRuleId(), debug = require("debug")("apigeelint:" + ruleId); @@ -26,14 +25,32 @@ const plugin = { message: "A check for a body element must be performed before policy execution.", fatal: false, - severity: 2, //error - nodeType: "ExtractVariables", + severity: 1, //1=warning, 2=error + nodeType: 'Step', enabled: true }; -const checker = require('./_extractVariablesCheck.js').check; +const EVAttachmentChecker = require('./_extractVariablesPayloadCheck.js'); +const checker = new EVAttachmentChecker(plugin, 'XMLPayload', debug); + +const onProxyEndpoint = function(endpoint, cb) { + debug('onProxyEndpoint'); + let flagged = checker.check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +const onTargetEndpoint = function(endpoint, cb) { + debug('onTargetEndpoint'); + let flagged = checker.check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; module.exports = { plugin, - onPolicy : checker(plugin, 'XMLPayload', debug) + onProxyEndpoint, + onTargetEndpoint }; diff --git a/lib/package/plugins/ST005-extractFormParamConditionCheck.js b/lib/package/plugins/ST005-extractFormParamConditionCheck.js new file mode 100644 index 00000000..374bfe5f --- /dev/null +++ b/lib/package/plugins/ST005-extractFormParamConditionCheck.js @@ -0,0 +1,162 @@ +/* + Copyright 2019-2022 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. +*/ + +//extractFormParamConditionCheck +//|   |:white_medium_square:| ST005 | Extract Variables with FormParam | A check for a body element must be performed before policy execution. | + +const ruleId = require("../myUtil.js").getRuleId(), + debug = require("debug")("apigeelint:" + ruleId), + xpath = require("xpath"); + +const plugin = { + ruleId, + name: "Extract Variables with FormParam", + message: + "For the ExtractVariables step, an appropriate check for a message body was not found on the enclosing Step or Flow.", + fatal: false, + severity: 1, //1=warning, 2=error + nodeType: 'Step', + enabled: true + }; + +const stringNonNullCheckRegexp = (source) => `\\(? *\\b${source}\\b *(!=|NotEquals|IsNot) *(\"\"|null) *\\)?`; + +const messageCheckRegexp = (source) => stringNonNullCheckRegexp(`${source}.formstring`); +const paramCheckRegexp = (source, name) => stringNonNullCheckRegexp(`${source}.formparam.${name}`); + +const exampleFormstringCondition = (source, params) => `${source}.formstring != null`; +const exampleParamCondition = + (source, params) => params.map(name => `${source}.formparam.${name} != null`).join(' || '); + +const isSufficient = + function(condition, source, paramNames) { + let exp = condition.getExpression(); + debug(`isSufficient cond(${exp})`); + let retval = exp.match(messageCheckRegexp(source)) || + paramNames.some( name => exp.match(paramCheckRegexp(source, name))); + debug(`> isSufficient() ${ !!retval}`); + return retval; + }; + +const check = + function(endpoint) { + let flagged = false; + let bundlePolicies = endpoint.parent.getPolicies(); + + endpoint.getSteps().forEach( step => { + let referredPolicy = bundlePolicies.find( p => p.getSteps().find(s => s == step)); + if ( !referredPolicy || referredPolicy.getType() !== "ExtractVariables") { + return; + } + debug('found an attached ExtractVariables policy'); + let formParam = xpath.select( + `/ExtractVariables/FormParam/text()`, + referredPolicy.getElement() + ); + if (formParam.length == 0) { + debug(`no extract from FormParam`); + return; + } + debug(`extracts from FormParam (${formParam.length})`); + let paramNames = xpath.select(`/ExtractVariables/FormParam/@name`,referredPolicy.getElement() ) + .map( attr => attr.value); + + debug(`param names: (${paramNames})`); + let sourceElement = xpath.select( + "/ExtractVariables/Source/text()", + referredPolicy.getElement() + ); + + let source = "will.be.replaced"; + if (sourceElement && sourceElement.length) { + source = sourceElement[0].data; + } + else { + // source is empty, ∴ implicitly use "message" + debug(`Source element is empty`); + source = 'message'; + } + + let condition = step.getCondition(); + if (condition && isSufficient(condition, source, paramNames)) { + debug('sufficient condition'); + return; + } + debug('did not find sufficient condition on step, checking parent...'); + let parent = step.parent; + if (parent) { + if (parent.getType() === "FlowPhase") { + debug(`parent phase: ${parent.getPhase()}`); + parent = parent.parent; + } + } + if (parent) { + if (parent.getType() === "Flow") { + condition = parent.getCondition(); + if (condition) { + debug(`parent cond expression: [${condition.getExpression()}]`); + if (isSufficient(condition, source, paramNames)) { + debug('parent has sufficient condition'); + return true; + } + debug('parent does not have sufficient condition'); + } + else { + debug(`no Condition on parent`); + } + } + } + else { + debug(`no parent?`); + } + + debug('missing or insufficient condition'); + flagged = true; + let policyName = xpath.select1('/ExtractVariables/@name', referredPolicy.getElement()).value; + endpoint.addMessage({ + plugin, + source: step.getElement().toString(), + line: step.getElement().lineNumber, + column: step.getElement().columnNumber, + message: `For the ExtractVariables step (${policyName}), an appropriate check for a message body was not found. For example, use ${exampleFormstringCondition(source)} or ${exampleParamCondition(source, paramNames)}` + }); + + }); + + return flagged; + }; + +const onProxyEndpoint = function(endpoint, cb) { + debug('onProxyEndpoint'); + let flagged = check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +const onTargetEndpoint = function(endpoint, cb) { + debug('onTargetEndpoint'); + let flagged = check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +module.exports = { + plugin, + onProxyEndpoint, + onTargetEndpoint +}; diff --git a/lib/package/plugins/ST006-threatProtectionJSONConditionCheck.js b/lib/package/plugins/ST006-threatProtectionJSONConditionCheck.js new file mode 100644 index 00000000..5a7f80e1 --- /dev/null +++ b/lib/package/plugins/ST006-threatProtectionJSONConditionCheck.js @@ -0,0 +1,56 @@ +/* + Copyright 2019-2022 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. +*/ + +//|   |:white_medium_square:| ST006 | JSON Threat Protection | A check for a body element must be performed before policy execution. | + +const ruleId = require("../myUtil.js").getRuleId(), + debug = require("debug")("apigeelint:" + ruleId); + +const plugin = { + ruleId, + name: "JSONThreatProtection check for body", + message: "A check for a body element should be performed before policy execution.", + fatal: false, + severity: 1, //1=warning, 2=error + nodeType: 'Step', + enabled: true + }; + +const checkMaker = require('./_policyConditionCheck.js'); + +const check = checkMaker(plugin, "JSONThreatProtection", debug); + +const onProxyEndpoint = function(endpoint, cb) { + debug('onProxyEndpoint'); + let flagged = check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +const onTargetEndpoint = function(endpoint, cb) { + debug('onTargetEndpoint'); + let flagged = check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +module.exports = { + plugin, + onProxyEndpoint, + onTargetEndpoint +}; diff --git a/lib/package/plugins/ST007-threatProtectionXMLConditionCheck.js b/lib/package/plugins/ST007-threatProtectionXMLConditionCheck.js new file mode 100644 index 00000000..7d8e6084 --- /dev/null +++ b/lib/package/plugins/ST007-threatProtectionXMLConditionCheck.js @@ -0,0 +1,55 @@ +/* + Copyright 2019-2021 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. +*/ + +//|   |:white_medium_square:| ST007 | XML Threat Protection | A check for a body element must be performed before policy execution. | + +const ruleId = require("../myUtil.js").getRuleId(), + debug = require("debug")("apigeelint:" + ruleId); + +const plugin = { + ruleId, + name: "XMLThreatProtection check for body", + message: "A check for a body element should be performed before policy execution.", + fatal: false, + severity: 1, //1=warning + nodeType: 'Step', + enabled: true + }; +const checkMaker = require('./_policyConditionCheck.js'); + +const check = checkMaker(plugin, "XMLThreatProtection", debug); + +const onProxyEndpoint = function(endpoint, cb) { + debug('onProxyEndpoint'); + let flagged = check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +const onTargetEndpoint = function(endpoint, cb) { + debug('onTargetEndpoint'); + let flagged = check(endpoint); + if (typeof(cb) == 'function') { + cb(null, flagged); + } + }; + +module.exports = { + plugin, + onProxyEndpoint, + onTargetEndpoint +}; diff --git a/lib/package/plugins/_extractVariablesCheck.js b/lib/package/plugins/_extractVariablesCheck.js deleted file mode 100644 index 283df4de..00000000 --- a/lib/package/plugins/_extractVariablesCheck.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - Copyright 2019-2022 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 xpath = require("xpath"); - -const stringNonNullCheckRegexp = (source) => `\\(? *\\b${source}\\b *(!=|NotEquals|IsNot) *(\"\"|null) *\\)?`; - -const messageCheckRegexp = (source) => stringNonNullCheckRegexp(`(${source}\.content|${source}.formstring)`); - -module.exports = { - check : (plugin, payloadType, debug) => - function(policy, cb) { - let reKnownMessages = "^(request|response|message)$"; - let condRegExp = "will.be.replaced"; - - let flagged = false; - if (policy.getType() === "ExtractVariables") { - debug('found ExtractVariables policy'); - if (policy.getSteps().length > 0) { - debug(`policy has ${policy.getSteps().length} steps`); - let payload = xpath.select( - `/ExtractVariables/${payloadType}/text()`, - policy.getElement() - ); - - if (payload.length > 0) { - debug(`is ${payloadType}`); - - let sourceElement = xpath.select( - "/ExtractVariables/Source/text()", - policy.getElement() - ); - - if (sourceElement.length) { - let source = sourceElement[0].data; - if (source.match(reKnownMessages)) { - condRegExp = messageCheckRegexp(source); - } - else { - // source is either a custom message, or a plain json string. - // Look for a check for either of those. - condRegExp = `(${messageCheckRegexp(source)}|${stringNonNullCheckRegexp(source)})`; - } - } - else { - // source is empty, ∴ implicitly use "message" - condRegExp = messageCheckRegexp('message'); - } - - debug(`condRegExp: ${condRegExp}`); - condRegExp = `^.*${condRegExp}.*$`; - - policy.getSteps().forEach(step => { - // let util = require('util'); - // debug('step ' + util.format(step)); - let condition = step.getCondition(); - if (condition) { - debug(`cond expression: [${condition.getExpression()}]`); - } - else { - debug('no Condition'); - } - if (condition && condition.getExpression().trim().match(condRegExp)) { - debug('sufficient condition'); - return; - } - - let parent = step.parent; - if (parent && parent.getType() === "FlowPhase") { - parent = parent.parent; - } - if (parent && parent.getType() === "Flow") { - debug('parent is Flow'); - condition = parent.getCondition(); - debug(`parent cond expression: [${condition.getExpression()}]`); - if (condition && condition.getExpression().trim().match(condRegExp)) { - debug('parent has sufficient condition'); - return; - } - } - debug('missing or insufficient condition'); - flagged = true; - policy.addMessage({ - plugin, - source: condition.getElement().toString(), - line: condition.getElement().lineNumber, - column: condition.getElement().columnNumber, - message: - "An appropriate check for a message body was not found on the enclosing Step or Flow." - }); - return ; - }); - - // if ( ! hasBodyChecks) { - // } - } - else { - debug('not a XMLPayload'); - } - } - else { - debug('not attached'); - } - } - if (typeof(cb) == 'function') { - cb(null, flagged); - } - } -}; diff --git a/lib/package/plugins/_extractVariablesPayloadCheck.js b/lib/package/plugins/_extractVariablesPayloadCheck.js new file mode 100644 index 00000000..a0df2b6f --- /dev/null +++ b/lib/package/plugins/_extractVariablesPayloadCheck.js @@ -0,0 +1,154 @@ +/* + Copyright 2019-2022 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 xpath = require("xpath"); + +const stringNonNullCheckRegexp = (source) => `\\(? *\\b${source}\\b *(!=|NotEquals|IsNot) *(\"\"|null) *\\)?`; + +const messageCheckRegexp = (source) => stringNonNullCheckRegexp(`${source}\.content`); + +const exampleCondition = (source) => `${source}.content != null`; + +class EVAttachmentChecker { + constructor(plugin, payloadType, debug) { + debug(`EVAttachmentChecker ctor (${payloadType})`); + this.plugin = plugin; + this.payloadType = payloadType; // JSONPayload or XMLPayload + this.debug = debug; + this.flagged = false; + debug(`EVAttachmentChecker ctor done`); + } + + check(endpoint) { + let debug = this.debug; + this.debug(`check (${endpoint.getType()})`); + let reKnownMessages = "^(request|response|message)$"; + let flagged = false; + let bundlePolicies = endpoint.parent.getPolicies(); + let checker = this; + + endpoint.getSteps().forEach( step => { + let referredPolicy = bundlePolicies.find( p => p.getSteps().find(s => s == step)); + if ( !referredPolicy || referredPolicy.getType() !== "ExtractVariables") { + return; + } + + debug(`found an attached ExtractVariables policy, line ${step.getElement().lineNumber}`); + let payload = xpath.select( + `/ExtractVariables/${checker.payloadType}/text()`, + referredPolicy.getElement() + ); + + if (payload.length == 0) { + debug(`no extract from ${checker.payloadType}`); + return; + } + debug(`extract from ${checker.payloadType}`); + + let sourceElement = xpath.select( + "/ExtractVariables/Source/text()", + referredPolicy.getElement() + ); + + let condRegExp = "will.be.replaced"; + let source = 'message'; // may be replaced + if (sourceElement && sourceElement.length) { + source = sourceElement[0].data || 'message'; + if (source.match(reKnownMessages)) { + condRegExp = messageCheckRegexp(source); + } + else { + // source is either a custom message, or a plain json or XML string. + // Look for a check for either of those. + condRegExp = `(${messageCheckRegexp(source)}|${stringNonNullCheckRegexp(source)})`; + } + } + else { + // No Source element, ∴ implicitly use "message" + debug(`Source element is missing or empty`); + condRegExp = messageCheckRegexp(source); + } + + debug(`condRegExp: ${condRegExp}`); + condRegExp = `^.*${condRegExp}.*$`; + + let condition = step.getCondition(); + debug(condition? + `cond expression: [${condition.getExpression()}]`: + 'no Condition'); + + if (condition && condition.getExpression().trim().match(condRegExp)) { + debug('sufficient condition'); + return; + } + debug('did not find sufficient condition on step, checking parent...'); + + // either there is a condition which is insufficient, or no condition. + // now check the paerent - maybe a condition there. + let parent = step.parent; + if (parent) { + if (parent.getType() === "FlowPhase") { + debug(`parent type: ${parent.getType()} phase: ${parent.getPhase()}... popping up.`); + parent = parent.parent; + } + } + if (parent) { + debug(`parent type: ${parent.getType()}`); + if (parent.getType() === "Flow" || parent.getType() === "FaultRule") { + debug('check parent condition'); + condition = parent.getCondition(); + if (condition) { + debug(`parent cond expression: [${condition.getExpression()}]`); + if (condition.getExpression().trim().match(condRegExp)) { + debug('parent has sufficient condition'); + return; + } + debug('parent does not have sufficient condition'); + } + else { + debug(`no Condition on parent`); + } + } + else { + debug(`not checking Condition on parent`); + } + } + else { + debug(`no parent?`); + } + + debug('missing or insufficient condition'); + flagged = true; + let policyName = xpath.select1('/ExtractVariables/@name', referredPolicy.getElement()).value; + endpoint.addMessage({ + plugin: checker.plugin, + source: step.getElement().toString(), + line: step.getElement().lineNumber, + column: step.getElement().columnNumber, + message: `For the ExtractVariables step (${policyName}), an appropriate check for a message body was not found. For example, use ${exampleCondition(source)}` + }); + + }); + + this.flagged = flagged; + return flagged; + + } + +} + + +module.exports = EVAttachmentChecker; diff --git a/lib/package/plugins/_policyConditionCheck.js b/lib/package/plugins/_policyConditionCheck.js index af2af4b7..99fc05e6 100644 --- a/lib/package/plugins/_policyConditionCheck.js +++ b/lib/package/plugins/_policyConditionCheck.js @@ -1,5 +1,5 @@ /* - Copyright 2019-2021 Google LLC + Copyright 2019-2022 Google LLC Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -14,52 +14,93 @@ limitations under the License. */ -module.exports = (plugin, typename, condRegExp, debug) => - function(policy, cb) { - let flagged = false; - if (policy.getType() === typename) { - debug(`found ${typename} policy`); - if (policy.getSteps().length > 0) { - let missingBodyCheck = false, - steps = policy.getSteps(); - - steps.forEach(function(step) { - let condition = step.getCondition(); - if (!condition || !condition.getExpression().match(condRegExp)) { - if (step.parent && step.parent.getType() === "Flow") { - let condition = step.parent.getCondition(); - if ( ! condition || !condition.getExpression().match(condRegExp)) { - debug('condition insufficient, and parent condition missing or insufficient'); - missingBodyCheck = true; - } - else { - debug('condition insufficient, but parent condition is sufficient'); +const xpath = require("xpath"); + +const stringNonNullCheckRegexp = (source) => `\\(? *\\b${source}\\b *(!=|NotEquals|IsNot) *(\"\"|null) *\\)?`; + +const messageCheckRegexp = (source) => stringNonNullCheckRegexp(`${source}.content`); + +const exampleCondition = (source) => `${source}.content != null`; + +const checkFactory = (plugin, policyType, debug) => + function(endpoint) { + let flagged = false; + let bundlePolicies = endpoint.parent.getPolicies(); + + endpoint.getSteps().forEach( step => { + let referredPolicy = bundlePolicies.find( p => p.getSteps().find(s => s == step)); + if ( ! referredPolicy || referredPolicy.getType() !== policyType) { + return; + } + debug(`found an attached ${policyType} policy`); + let sourceElement = xpath.select( + `/${policyType}/Source/text()`, + referredPolicy.getElement() + ); + + let condRegExp = "will.be.replaced"; + let source = 'message'; + if (sourceElement && sourceElement.length) { + source = sourceElement[0].data || 'message'; + condRegExp = messageCheckRegexp(source); + } + else { + // No Source element, ∴ implicitly use "message" + debug(`Source element is missing or empty`); + condRegExp = messageCheckRegexp('message'); + } + + let condition = step.getCondition(); + if (condition && condition.getExpression().match(condRegExp)) { + debug('sufficient condition'); + return; + } + debug('did not find sufficient condition on step, checking parent...'); + let parent = step.parent; + if (parent) { + if (parent.getType() === "FlowPhase") { + debug(`parent phase: ${parent.getPhase()}`); + parent = parent.parent; + } + } + if (parent) { + if (parent.getType() === "Flow") { + condition = parent.getCondition(); + if (condition) { + debug(`parent cond expression: [${condition.getExpression()}]`); + if (condition.getExpression().match(condRegExp)) { + debug('parent has sufficient condition'); + return true; } + debug('parent does not have sufficient condition'); } else { - debug('no condition or insufficient condition'); - missingBodyCheck = true; + debug(`no Condition on parent`); } } - else { - debug('condition looks good'); - } + } + else { + debug(`no parent?`); + } + + debug('missing or insufficient condition'); + flagged = true; + let policyName = xpath.select1(`/${policyType}/@name`, referredPolicy.getElement()).value; + + endpoint.addMessage({ + plugin, + source: step.getElement().toString(), + line: step.getElement().lineNumber, + column: step.getElement().columnNumber, + message: + `For the ${policyType} step (${policyName}), an appropriate check for a message body was not found. For example, use ${exampleCondition(source)}` }); - if (missingBodyCheck) { - flagged = true; - policy.addMessage({ - plugin, - message: - "An appropriate check for a message body was not found on the enclosing Step or Flow." - }); - } - } - else { - debug('not attached'); - } - } - if (typeof(cb) == 'function') { - cb(null, flagged); - } -}; + }); + + return flagged; + }; + + + +module.exports = checkFactory; diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/ev-attachment.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/ev-attachment.xml new file mode 100644 index 00000000..e86ef94b --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/ev-attachment.xml @@ -0,0 +1,10 @@ + + + 1489339923158 + dino + + ev-attachment + 1489340418969 + orgAdmin + + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-Formparam-1.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-Formparam-1.xml new file mode 100644 index 00000000..5cba7a55 --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-Formparam-1.xml @@ -0,0 +1,10 @@ + + request + + hello {user} + + + welcome {person} + + formp + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-JSONPayload-1.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-JSONPayload-1.xml new file mode 100644 index 00000000..9d22465e --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-JSONPayload-1.xml @@ -0,0 +1,10 @@ + + true + + + $.errorCode + + + response + extracted + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-URIPath-1.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-URIPath-1.xml new file mode 100644 index 00000000..773c2c6b --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-URIPath-1.xml @@ -0,0 +1,10 @@ + + request + extracted + + + + /accounts/{id} + + true + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-XMLPayload-response.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-XMLPayload-response.xml new file mode 100644 index 00000000..7c6b9685 --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-XMLPayload-response.xml @@ -0,0 +1,12 @@ + + response + + + http://schemas.xmlsoap.org/soap/envelope/ + + + local-name(/soap:Envelope/soap:Body/*[1]) + + + soapresponse + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-XMLPayload-scResponse.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-XMLPayload-scResponse.xml new file mode 100644 index 00000000..27228b82 --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/EV-XMLPayload-scResponse.xml @@ -0,0 +1,12 @@ + + scResponse + + + http://schemas.xmlsoap.org/soap/envelope/ + + + local-name(/soap:Envelope/soap:Body/*[1]) + + + soapresponse + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/RF-UnknownRequest.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/RF-UnknownRequest.xml new file mode 100644 index 00000000..c92bcb3f --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/policies/RF-UnknownRequest.xml @@ -0,0 +1,16 @@ + + true + + + { + "error" : { + "code" : 404.01, + "message" : "that request was unknown; try a different request." + } +} + + 404 + Not Found + + + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/proxies/endpoint1.xml b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/proxies/endpoint1.xml new file mode 100644 index 00000000..b7f59c36 --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/apiproxy/proxies/endpoint1.xml @@ -0,0 +1,301 @@ + + Proxy Endpoint 1 + + /ev-attachment + + secure + + + + + + + EV-XMLPayload-response + + response.content != null + + + + + + response.content != null + EV-XMLPayload-response + + fault.name = "foobar" + + + + + + response.header.content-type = "application/xml" + EV-XMLPayload-response + + fault.name = "foobar" + + + + + + EV-XMLPayload-response + + + + + + + + + + request.content != null + EV-Formparam-1 + + + + + request.formparams.count != 0 + EV-Formparam-1 + + + + + request.formparam.greeting != null + EV-Formparam-1 + + + + + request.formstring != null + EV-Formparam-1 + + + + + request.formparam.hello != null + EV-Formparam-1 + + + + + + + + + + + + + + + + + + + + + + + + + + + EV-JSONPayload-1 + + + (proxy.pathsuffix MatchesPath "/json-1") and (request.verb = "GET") + + + + + + + + + + EV-JSONPayload-1 + + + (proxy.pathsuffix MatchesPath "/json-2") and (request.verb = "GET") + + + + + + + + + response.header.content-type = "application/json" + EV-JSONPayload-1 + + + (proxy.pathsuffix MatchesPath "/json-3") and (request.verb = "GET") + + + + + + + + + response.content != null + EV-JSONPayload-1 + + + (proxy.pathsuffix MatchesPath "/json-4") and (request.verb = "GET") + + + + + + + + + EV-JSONPayload-1 + + + (proxy.pathsuffix MatchesPath "/json-5") and (request.verb = "GET") and (response.content != null) + + + + + + + + + + EV-XMLPayload-response + + + (proxy.pathsuffix MatchesPath "/xml-1") and (request.verb = "GET") + + + + + + + + + + EV-XMLPayload-response + + + (proxy.pathsuffix MatchesPath "/xml-2") and (request.verb = "GET") + + + + + + + + + response.header.content-type = "application/xml" + EV-XMLPayload-response + + + (proxy.pathsuffix MatchesPath "/xml-3") and (request.verb = "GET") + + + + + + + + + response.content != null + EV-XMLPayload-response + + + (proxy.pathsuffix MatchesPath "/xml-4") and (request.verb = "GET") + + + + + + + + + EV-XMLPayload-response + + + (proxy.pathsuffix MatchesPath "/xml-5") and (request.verb = "GET") and (response.content != null) + + + + + + + + + scResponse.content != null + EV-XMLPayload-scResponse + + + (proxy.pathsuffix MatchesPath "/xml-6") and (request.verb = "GET") + + + + + + + + + response.content != null + EV-XMLPayload-scResponse + + + (proxy.pathsuffix MatchesPath "/xml-6") and (request.verb = "GET") + + + + + + + + + + EV-Formparam-1 + + + (proxy.pathsuffix MatchesPath "/form-1") and (request.verb = "GET") + + + + + + + + + request.formstring != null + EV-Formparam-1 + + + (proxy.pathsuffix MatchesPath "/form-1") and (request.verb = "GET") + + + + + + + + + EV-URIPath-1 + + + (proxy.pathsuffix MatchesPath "/form-2") and (request.verb = "GET") + + + + + + + + + RF-UnknownRequest + + + + + + + + + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/Shared-Flow-1.xml b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/Shared-Flow-1.xml new file mode 100644 index 00000000..577126f3 --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/Shared-Flow-1.xml @@ -0,0 +1,22 @@ + + + + 1486431487439 + dchiesa@google.com + + Shared-Flow-1 + 1486431673737 + dchiesa@google.com + + JavaScript-1 + + + jsc://JavaScript-1.js + + + SharedFlow + false + + default + + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-Formparam-1.xml b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-Formparam-1.xml new file mode 100644 index 00000000..38a1fbef --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-Formparam-1.xml @@ -0,0 +1,7 @@ + + request + + hello {user} + + formp + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-JSONPayload-1.xml b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-JSONPayload-1.xml new file mode 100644 index 00000000..9d22465e --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-JSONPayload-1.xml @@ -0,0 +1,10 @@ + + true + + + $.errorCode + + + response + extracted + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-URIPath-1.xml b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-URIPath-1.xml new file mode 100644 index 00000000..773c2c6b --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-URIPath-1.xml @@ -0,0 +1,10 @@ + + request + extracted + + + + /accounts/{id} + + true + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-XMLPayload-response.xml b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-XMLPayload-response.xml new file mode 100644 index 00000000..7c6b9685 --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/policies/EV-XMLPayload-response.xml @@ -0,0 +1,12 @@ + + response + + + http://schemas.xmlsoap.org/soap/envelope/ + + + local-name(/soap:Envelope/soap:Body/*[1]) + + + soapresponse + diff --git a/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/sharedflows/sf-default.xml b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/sharedflows/sf-default.xml new file mode 100644 index 00000000..51ca1d58 --- /dev/null +++ b/test/fixtures/resources/ExtractVariables-Attachment/sharedflowbundle/sharedflows/sf-default.xml @@ -0,0 +1,55 @@ + + + + + + EV-JSONPayload-1 + + + + + + + EV-XMLPayload-response + + + + + + + + response.content != null + EV-JSONPayload-1 + + + + + + + response.content != null + EV-XMLPayload-response + + + + + + + EV-Formparam-1 + + + + + + + request.formstring != null + EV-Formparam-1 + + + + + + + EV-URIPath-1 + + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/EV-URIPath-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/EV-URIPath-1.xml new file mode 100644 index 00000000..773c2c6b --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/EV-URIPath-1.xml @@ -0,0 +1,10 @@ + + request + extracted + + + + /accounts/{id} + + true + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/JTP-request-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/JTP-request-1.xml new file mode 100644 index 00000000..df039879 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/JTP-request-1.xml @@ -0,0 +1,8 @@ + + request + 20 + 10 + 15 + 50 + 500 + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/RF-UnknownRequest.xml b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/RF-UnknownRequest.xml new file mode 100644 index 00000000..c92bcb3f --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/RF-UnknownRequest.xml @@ -0,0 +1,16 @@ + + true + + + { + "error" : { + "code" : 404.01, + "message" : "that request was unknown; try a different request." + } +} + + 404 + Not Found + + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/XTP-myRequest-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/XTP-myRequest-1.xml new file mode 100644 index 00000000..48987255 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/XTP-myRequest-1.xml @@ -0,0 +1,22 @@ + + myRequest + + 10 + 10 + 10 + 10 + + + 5 + 2 + 3 + 3 + + + 15 + 10 + 10 + 10 + 10 + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/XTP-request-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/XTP-request-1.xml new file mode 100644 index 00000000..fdc83145 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/policies/XTP-request-1.xml @@ -0,0 +1,22 @@ + + request + + 10 + 10 + 10 + 10 + + + 5 + 2 + 3 + 3 + + + 15 + 10 + 10 + 10 + 10 + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/proxies/endpoint1.xml b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/proxies/endpoint1.xml new file mode 100644 index 00000000..93f396f5 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/proxies/endpoint1.xml @@ -0,0 +1,116 @@ + + Proxy Endpoint 1 + + /tp-attachment + + secure + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + JTP-request-1 + + + (proxy.pathsuffix MatchesPath "/jtp-1") and (request.verb = "POST") + + + + + + + + request.content != null + JTP-request-1 + + + (proxy.pathsuffix MatchesPath "/jtp-2") and (request.verb = "POST") + + + + + + + + XTP-request-1 + + + (proxy.pathsuffix MatchesPath "/xtp-1") and (request.verb = "POST") + + + + + + + + request.content != null + XTP-request-1 + + + (proxy.pathsuffix MatchesPath "/xtp-2") and (request.verb = "POST") + + + + + + + + myRequest.content != null + XTP-myRequest-1 + + + (proxy.pathsuffix MatchesPath "/xtp-3") and (request.verb = "POST") + + + + + + + + request.content != null + XTP-myRequest-1 + + + (proxy.pathsuffix MatchesPath "/xtp-4") and (request.verb = "POST") + + + + + + + + RF-UnknownRequest + + + + + + + + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/tp-attachment.xml b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/tp-attachment.xml new file mode 100644 index 00000000..f58184b8 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/apiproxy/tp-attachment.xml @@ -0,0 +1,10 @@ + + + 1489339923158 + dino + + tp-attachment + 1489340418969 + orgAdmin + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/Shared-Flow-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/Shared-Flow-1.xml new file mode 100644 index 00000000..577126f3 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/Shared-Flow-1.xml @@ -0,0 +1,22 @@ + + + + 1486431487439 + dchiesa@google.com + + Shared-Flow-1 + 1486431673737 + dchiesa@google.com + + JavaScript-1 + + + jsc://JavaScript-1.js + + + SharedFlow + false + + default + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/EV-URIPath-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/EV-URIPath-1.xml new file mode 100644 index 00000000..773c2c6b --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/EV-URIPath-1.xml @@ -0,0 +1,10 @@ + + request + extracted + + + + /accounts/{id} + + true + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/JTP-request-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/JTP-request-1.xml new file mode 100644 index 00000000..f1f35c0b --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/JTP-request-1.xml @@ -0,0 +1,8 @@ + + 20 + 10 + 15 + 50 + request + 500 + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/XTP-request-1.xml b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/XTP-request-1.xml new file mode 100644 index 00000000..fdc83145 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/policies/XTP-request-1.xml @@ -0,0 +1,22 @@ + + request + + 10 + 10 + 10 + 10 + + + 5 + 2 + 3 + 3 + + + 15 + 10 + 10 + 10 + 10 + + diff --git a/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/sharedflows/sf-default.xml b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/sharedflows/sf-default.xml new file mode 100644 index 00000000..23967f27 --- /dev/null +++ b/test/fixtures/resources/ThreatProtection-Attachment/sharedflowbundle/sharedflows/sf-default.xml @@ -0,0 +1,35 @@ + + + + + JTP-request-1 + + + + + + XTP-request-1 + + + + + + request.content != null + JTP-request-1 + + + + + + request.content != null + XTP-request-1 + + + + + + + EV-URIPath-1 + + + diff --git a/test/specs/EP002-elementPlacement.js b/test/specs/EP002-elementPlacement.js index e615df59..7e5ad37c 100644 --- a/test/specs/EP002-elementPlacement.js +++ b/test/specs/EP002-elementPlacement.js @@ -21,7 +21,7 @@ const assert = require("assert"), util = require('util'), bl = require("../../lib/package/bundleLinter.js"); -describe(`EP002 - bundle with misplaced elements`, () => { +describe(`EP002 - apiproxy bundle with misplaced elements`, () => { let configuration = { debug: true, @@ -117,3 +117,39 @@ describe(`EP002 - bundle with misplaced elements`, () => { }); }); + +describe(`EP002 - sharedflowbundle with no misplaced elements`, () => { + + let configuration = { + debug: true, + source: { + type: "filesystem", + path: path.resolve(__dirname, '../fixtures/resources/ExtractVariables-Attachment/sharedflowbundle'), + bundleType: "sharedflowbundle" + }, + profile: 'apigeex', + excluded: {}, + setExitCode: false, + output: () => {} // suppress output + }; + + bl.lint(configuration, (bundle) => { + let items = bundle.getReport(); + + it('should generate some errors', () => { + assert.ok(items); + assert.ok(items.length); + let itemsWithErrors = items.filter(item => item.messages && item.messages.length); + assert.equal(itemsWithErrors.length, 1); + }); + + it('should generate no EP002 errors', () => { + let ep002Errors = items.filter(item => item.messages && item.messages.length && + item.messages.find(m => m.ruleId == 'EP002')); + + assert.equal(ep002Errors.length, 0); + }); + + }); + +}); diff --git a/test/specs/PO001-JSONThreatProtection-ConditionCheck.js b/test/specs/PO001-JSONThreatProtection-ConditionCheck.js deleted file mode 100644 index 326e02ba..00000000 --- a/test/specs/PO001-JSONThreatProtection-ConditionCheck.js +++ /dev/null @@ -1,197 +0,0 @@ -/* - Copyright 2019-2021 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. -*/ -/* global it, describe */ - -const assert = require("assert"), - testID = "PO001", - debug = require("debug")("apigeelint:" + testID), - bl = require("../../lib/package/bundleLinter.js"), - Bundle = require("../../lib/package/Bundle.js"), - Policy = require("../../lib/package/Policy.js"), - Step = require("../../lib/package/Step.js"), - Flow = require("../../lib/package/Flow.js"), - plugin = require(bl.resolvePlugin(testID)), - expectedMessage = 'An appropriate check for a message body was not found on the enclosing Step or Flow.', - Dom = require("@xmldom/xmldom").DOMParser, - test = function(caseNum, exp, stepExp, flowExp, assertion) { - it(`test case ${caseNum}, expect(${assertion})`, - function() { - let pDoc = new Dom().parseFromString(exp), - sDoc, - fDoc, - p = new Policy(pDoc.documentElement, this), - s, - f; - - let origAddMessage = p.addMessage; - p.addMessage = function(msg) { - //debug(msg); - origAddMessage.call(p, msg); - }; - p.getElement = function() { - return pDoc.documentElement; - }; - p.getSteps = function() { - if (s) return [s]; - return []; - }; - - if (flowExp) { - fDoc = new Dom().parseFromString(flowExp); - f = new Flow(fDoc.documentElement, null); - } - - if (stepExp) { - sDoc = new Dom().parseFromString(stepExp); - s = new Step(sDoc.documentElement, f); - } - - plugin.onPolicy(p, function(e, flagged) { - assert.equal(e, undefined, e ? " error " : " no error"); - assert.equal( - flagged, - assertion, - flagged - ? "warning/error was returned" - : "warning/error was not returned" - ); - if (flagged) { - let report = p.getReport(), - msgs = report.messages; - assert.ok(msgs, "missing messages"); - assert.equal(msgs.length, 1, "incorrect number of messages"); - assert.equal(msgs[0].message, expectedMessage); - } - }); - } - ); - }; - -// This test does not test the policy config, but rather the policy attachment. -// So we can use the same policy configuration for every test case. -const policyXml = ` - JSON Threat Protection 1 - 20 - 10 - 15 - 50 - request - 500 -`; - - -describe(`${testID} - ${plugin.plugin.name}`, function() { - test( - 1, - policyXml, - null, - null, - false //not attached - ); - - test( - 2, - policyXml, - ` - message.content != "" - JSON-Threat-Protection-1 - `, - null, - false //attached good condition - ); - - - test( - 3, - policyXml, - ` - foo != "" - JSON-Threat-Protection-1 -`, - null, - true //attached insufficient condition - ); - - - test( - 4, - policyXml, - ` - foo != "" - JSON-Threat-Protection-1 -`, - ` - - foo != "" - JSON-Threat-Protection-1 - - - `, - true //attached insufficient condition - ); - - test( - 5, policyXml, - ` - foo != "" - JSON-Threat-Protection-1 - `, - ` - - foo != "" - JSON-Threat-Protection-1 - - message.content != "" - `, - false //attached sufficient condition - ); - - test( - 6, - 'regExLookAroundrequestfalse(?/(@?[w_?w:*]+([[^]]+])*)?)+', - null, - null, - false //not JSONThreatProtection - ); - -}); - - -describe(`${testID} - Print plugin results`, function() { - debug("test configuration: " + JSON.stringify(configuration)); - var bundle = new Bundle(configuration); - bl.executePlugin(testID, bundle); - let report = bundle.getReport(); - - it("should create a report object with valid schema", function() { - - let formatter = bl.getFormatter("json.js"); - if (!formatter) { - assert.fail("formatter implementation not defined"); - } - let schema = require("./../fixtures/reportSchema.js"), - Validator = require("jsonschema").Validator, - v = new Validator(), - jsonReport = JSON.parse(formatter(report)), - validationResult = v.validate(jsonReport, schema); - assert.equal( - validationResult.errors.length, - 0, - validationResult.errors - ); - }); - -}); diff --git a/test/specs/PO002-XMLThreatProtection-ConditionCheck.js b/test/specs/PO002-XMLThreatProtection-ConditionCheck.js deleted file mode 100644 index 9ba75cae..00000000 --- a/test/specs/PO002-XMLThreatProtection-ConditionCheck.js +++ /dev/null @@ -1,214 +0,0 @@ -/* - Copyright 2019-2021 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. -*/ -/* global it, describe */ - -const assert = require("assert"), - util = require("util"), - testID = "PO002", - debug = require("debug")("apigeelint:" + testID), - Bundle = require("../../lib/package/Bundle.js"), - bl = require("../../lib/package/bundleLinter.js"), - Policy = require("../../lib/package/Policy.js"), - Step = require("../../lib/package/Step.js"), - Flow = require("../../lib/package/Flow.js"), - plugin = require(bl.resolvePlugin(testID)), - Dom = require("@xmldom/xmldom").DOMParser, - test = function(caseNum, exp, stepExp, flowExp, assertion) { - it(`tests case ${caseNum}, expect(${assertion})`, - function() { - let pDoc = new Dom().parseFromString(exp), - sDoc, - fDoc, - p = new Policy(pDoc.documentElement, this), - s, - f; - - let origAddMessage = p.addMessage; - p.addMessage = function(msg) { - //debug(msg); - origAddMessage.call(p, msg); - }; - p.getElement = function() { - return pDoc.documentElement; - }; - p.getSteps = function() { - if (s) return [s]; - return []; - }; - - if (flowExp) { - fDoc = new Dom().parseFromString(flowExp); - f = new Flow(fDoc.documentElement, null); - } - - if (stepExp) { - sDoc = new Dom().parseFromString(stepExp); - s = new Step(sDoc.documentElement, f); - } - - plugin.onPolicy(p, function(e, flagged) { - assert.equal(e, undefined, e ? " error " : " no error"); - assert.equal( - flagged, - assertion, - flagged - ? "warning/error was returned" - : "warning/error was not returned" - ); - }); - } - ); - }; - -// This test does not test the policy config, but rather the policy attachment. -// So we can use the same policy configuration for every test case. -const policyXml = - ` - XML Threat Protection 1 - - 10 - 10 - 10 - 10 - - request - - 5 - 2 - 3 - 3 - - - 15 - 10 - 10 - 10 - 10 - - `; - -describe(`${testID} - ${plugin.plugin.name}`, function() { - - test( - 1, - policyXml, - null, - null, - false //not attached - ); - - test( - 2, - policyXml, - ` - message.content != "" - XML-Threat-Protection-1 - `, - null, - false //attached good condition - ); - - test( - 3, - policyXml, - ` - foo != "" - XML-Threat-Protection-1 - `, - null, - true //attached insufficient condition - ); - - test( - 4, - policyXml, - ` - foo != "" - XML-Threat-Protection-1 - `, - ` - - foo != "" - XML-Threat-Protection-1 - - - `, - true //attached insufficient condition - ); - - test( - 5, - policyXml, - ` - foo != "" - XML-Threat-Protection-1 - `, - ` - - foo != "" - XML-Threat-Protection-1 - - message.content != "" - `, - false //attached sufficient condition - ); - - test( - 6, - 'regExLookAroundrequestfalse(?/(@?[w_?w:*]+([[^]]+])*)?)+', - null, - null, - false //not extractVar - ); - -}); - -describe(`${testID} - Print plugin results`, function() { - debug("test configuration: " + JSON.stringify(configuration)); - - let bundle = new Bundle(configuration); - bl.executePlugin(testID, bundle); - let report = bundle.getReport(); - debug("raw report: \n" + util.format(report)); - - it("should create a report object with valid schema", function() { - let formatter = bl.getFormatter("json.js"); - - if (!formatter) { - assert.fail("formatter implementation not defined"); - } - let schema = require("./../fixtures/reportSchema.js"), - Validator = require("jsonschema").Validator, - v = new Validator(), - formattedReport = formatter(report), - parsedReport = JSON.parse(formattedReport), - validationResult = v.validate(parsedReport, schema); - debug("json formatted report: \n" + formattedReport); - assert.equal( - validationResult.errors.length, - 0, - validationResult.errors - ); - }); - - it("should create a unixstyle report", function() { - let unixFormatter = bl.getFormatter("unix.js"), - formattedReport = unixFormatter(report); - debug("unix formatted report: \n" + formattedReport); - assert.ok(report); - }); - -}); diff --git a/test/specs/PO003-ExtractJSON-ConditionCheck.js b/test/specs/PO003-ExtractJSON-ConditionCheck.js deleted file mode 100644 index d039cd07..00000000 --- a/test/specs/PO003-ExtractJSON-ConditionCheck.js +++ /dev/null @@ -1,344 +0,0 @@ -/* - Copyright 2019-2022 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. -*/ -/* global describe, it, configuration */ - -const assert = require("assert"), - testID = "PO003", - debug = require("debug")("apigeelint:" + testID), - Bundle = require("../../lib/package/Bundle.js"), - bl = require("../../lib/package/bundleLinter.js"), - Policy = require("../../lib/package/Policy.js"), - Step = require("../../lib/package/Step.js"), - Flow = require("../../lib/package/Flow.js"), - plugin = require(bl.resolvePlugin(testID)), - Dom = require("@xmldom/xmldom").DOMParser, - test = function(caseNum, exp, stepExp, flowExp, assertion) { - it(`test case ${caseNum}, expected(${assertion})`, - function() { - var pDoc = new Dom().parseFromString(exp), - sDoc, - fDoc, - p = new Policy(pDoc, this), - s, - f; - - let origAddMessage = p.addMessage; - p.addMessage = function(msg) { - //debug(msg); - origAddMessage.call(p, msg); - }; - - p.getElement = function() { - return pDoc; - }; - p.getSteps = function() { - if (s) return [s]; - return []; - }; - - if (flowExp) { - fDoc = new Dom().parseFromString(flowExp); - f = new Flow(fDoc.documentElement, null); - } - - if (stepExp) { - sDoc = new Dom().parseFromString(stepExp); - s = new Step(sDoc.documentElement, f); - } - - plugin.onPolicy(p, function(e, flagged) { - assert.equal(e, undefined, e ? " error " : " no error"); - assert.equal( - flagged, - assertion, - flagged - ? "warning/error was returned" - : "warning/error was not returned" - ); - }); - } - ); - }; - -const policyXml = { - test1: ` - response - - - $.results[0].geometry.location.lat - - - $.results[0].geometry.location.lng - - - - hello {user} - - geocoderesponse - `, - test2: ` - scResponse - - - http://schemas.xmlsoap.org/soap/envelope/ - - - local-name(/soap:Envelope/soap:Body/*[1]) - - - soapresponse -`, - test3 : ` - private.geis.kvm.api.config - - - $.targets.location - - - config -`, - test4: ` - request - false - - (?/(@?[w_?w:*]+([[^]]+])*)?)+ - -` - }; - -describe(`${testID} - ${plugin.plugin.name}`, function() { - - test( - 10, - policyXml.test1, - null, - null, - false //not attached - ); - - test( - 20, - policyXml.test1, - ` - foo != "" - EV--Policy-Name-Does-Not-Matter - `, - null, - true // attached insufficient condition - ); - - test( - 30, - policyXml.test2, - ` - foo != "" - EV--Policy-Name-Does-Not-Matter - `, - null, - false // attached, insufficient condition, but not JSONPayload - ); - - test( - 40, - policyXml.test1, - ` - response.content != "" - EV--Policy-Name-Does-Not-Matter -`, - null, - false //attached, sufficient condition - ); - - test( - 41, - policyXml.test1, - ` - response.content !="" - EV--Policy-Name-Does-Not-Matter -`, - null, - false //attached, sufficient condition - ); - - test( - 42, - policyXml.test1, - ` - (response.content !=null) and (response.status.code !="401" ) - EV--Policy-Name-Does-Not-Matter -`, - null, - false //attached, sufficient condition - ); - - test( - 50, - policyXml.test1, - ` - (response.content != "") - EV--Policy-Name-Does-Not-Matter -`, - null, - false //attached, sufficient condition with parenthesis - ); - - test( - 51, - policyXml.test1, - ` - ( response.content != "") - EV--Policy-Name-Does-Not-Matter -`, - null, - false //attached, sufficient condition with parenthesis - ); - - test( - 52, - policyXml.test1, - ` - ( response.content != "") - EV--Policy-Name-Does-Not-Matter -`, - null, - false //attached, sufficient condition - ); - - test( - 53, - policyXml.test1, - ` - ( response.content !="" ) - EV--Policy-Name-Does-Not-Matter -`, - null, - false //attached, sufficient condition - ); - - test( - 60, - policyXml.test1, - ` - message.content != "" - EV--Policy-Name-Does-Not-Matter -`, - null, - true //attached insufficient condition. condition var does not match policy. - ); - - test( - 70, - policyXml.test1, - ` - foo != "" - EV--Policy-Name-Does-Not-Matter -`, - ` - - foo != "" - EV--Policy-Name-Does-Not-Matter - - - `, - true //attached insufficient condition - ); - - test( - 80, - policyXml.test1, - ` - foo != "" - EV--Policy-Name-Does-Not-Matter - `, - ` - - foo != "" - EV--Policy-Name-Does-Not-Matter - - response.content != "" - `, - false //attached sufficient condition in parent - ); - - test( - 90, - policyXml.test4, - null, - null, - false //not ExtractVariables - ); - - test( - 100, - policyXml.test3, - ` - private.geis.kvm.api.config != "" - EV-3 -`, - null, - false // attached sufficient condition - ); - - test( - 110, - policyXml.test3, - ` - private.geis.kvm.api.config IsNot null - EV-3 -`, - null, - false //attached sufficient condition - ); - - test( - 120, - policyXml.test3, - ` - private.geis.kvm.api != "" - EV-3 -`, - null, - true //attached insufficient condition - ); - -}); - - -describe(`${testID} - Print plugin results`, function() { - debug("test configuration: " + JSON.stringify(configuration)); - var bundle = new Bundle(configuration); - bl.executePlugin(testID, bundle); - let report = bundle.getReport(); - - it("should create a report object with valid schema", function() { - - let formatter = bl.getFormatter("json.js"); - if (!formatter) { - assert.fail("formatter implementation not defined"); - } - let schema = require("./../fixtures/reportSchema.js"), - Validator = require("jsonschema").Validator, - v = new Validator(), - jsonReport = JSON.parse(formatter(report)), - validationResult = v.validate(jsonReport, schema); - assert.equal( - validationResult.errors.length, - 0, - validationResult.errors - ); - }); - -}); diff --git a/test/specs/PO004-ExtractXML-ConditionCheck.js b/test/specs/PO004-ExtractXML-ConditionCheck.js deleted file mode 100644 index 38030eda..00000000 --- a/test/specs/PO004-ExtractXML-ConditionCheck.js +++ /dev/null @@ -1,308 +0,0 @@ -/* - Copyright 2019-2021 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. -*/ -/* global describe, it, configuration */ - -const assert = require("assert"), - testID = "PO004", - debug = require("debug")("apigeelint:" + testID), - Bundle = require("../../lib/package/Bundle.js"), - bl = require("../../lib/package/bundleLinter.js"), - Policy = require("../../lib/package/Policy.js"), - Step = require("../../lib/package/Step.js"), - Flow = require("../../lib/package/Flow.js"), - plugin = require(bl.resolvePlugin(testID)), - Dom = require("@xmldom/xmldom").DOMParser, - test = function(caseNum, exp, stepExp, flowExp, assertion) { - it(`test case ${caseNum}, expected(${assertion})`, - function() { - var pDoc = new Dom().parseFromString(exp), - sDoc, - fDoc, - p = new Policy(pDoc, this), - s, - f; - - let origAddMessage = p.addMessage; - p.addMessage = function(msg) { - debug(msg); - origAddMessage.call(p, msg); - }; - - p.getElement = function() { - return pDoc; - }; - p.getSteps = function() { - if (s) return [s]; - return []; - }; - - if (flowExp) { - fDoc = new Dom().parseFromString(flowExp); - f = new Flow(fDoc.documentElement, null); - } - - if (stepExp) { - sDoc = new Dom().parseFromString(stepExp); - s = new Step(sDoc.documentElement, f); - } - - plugin.onPolicy(p, function(e, flagged) { - assert.equal(e, undefined, e ? " error " : " no error"); - assert.equal( - flagged, - assertion, - flagged - ? "warning/error was returned" - : "warning/error was not returned" - ); - }); - } - ); - }; - - -const policyXml = { - test1: ` - response - - - $.results[0].geometry.location.lat - - - $.results[0].geometry.location.lng - - - geocoderesponse - `, -test2: ` - scResponse - - - http://schemas.xmlsoap.org/soap/envelope/ - - - local-name(/soap:Envelope/soap:Body/*[1]) - - - soapresponse -`, - test3 : ` - private.geis.kvm.api.config - - - /config/target/text() - - - config -`, - test4: ` - request - false - - (?/(@?[w_?w:*]+([[^]]+])*)?)+ - -`, - test5: ` - request - - - /config/target/text() - - - extracted - ` - }; - - -describe(`${testID} - ${plugin.plugin.name}`, function() { - - test( - 10, - policyXml.test2, - null, - null, - false // not attached - ); - - test( - 20, - policyXml.test2, - ` - foo != "" - EV--Policy-Name-Does-Not-Matter - `, - null, - true // attached insufficient condition - ); - - test( - 30, - policyXml.test1, - ` - foo != "" - EV--Policy-Name-Does-Not-Matter - `, - null, - false // attached, insufficient condition, but not XMLPayload - ); - - test( - 40, - policyXml.test2, - ` - scResponse.content != "" - EV--Policy-Name-Does-Not-Matter -`, - null, - false // attached good condition - ); - - test( - 41, - policyXml.test5, - ` - (request.content != null) - EV--Policy-Name-Does-Not-Matter -`, - null, - false // attached good condition - ); - - test( - 42, - policyXml.test5, - ` - (notrequest.content != null) - EV--Policy-Name-Does-Not-Matter -`, - null, - true // attached, bad condition - ); - - test( - 50, - policyXml.test2, - ` - message.content != "" - EV--Policy-Name-Does-Not-Matter -`, - null, - true //attached insufficient condition - ); - - test( - 60, - policyXml.test2, - ` - foo != "" - EV--Policy-Name-Does-Not-Matter -`, - ` - - foo != "" - ExtractVariables-5 - - - `, - true //attached insufficient condition - ); - - test( - 70, - policyXml.test2, - ` - foo != "" - ExtractVariables-6 - `, - ` - - foo != "" - ExtractVariables-6 - - scResponse.content != "" - `, - false //attached sufficient condition - ); - - test( - 80, - policyXml.test4, - null, - null, - false //not ExtractVariables - ); - - test( - 90, - policyXml.test3, - ` - private.geis.kvm.api.config != "" - EV-3 -`, - null, - false //attached sufficient condition - ); - - test( - 91, - policyXml.test3, - ` - private.geis.kvm.api.config IsNot null - EV-3 -`, - null, - false //attached sufficient condition - ); - - test( - 100, - policyXml.test3, - ` - private.geis.kvm.api != "" - EV-3 -`, - null, - true //attached insufficient condition - ); - -}); - - -describe(`${testID} - Print plugin results`, function() { - debug("test configuration: " + JSON.stringify(configuration)); - var bundle = new Bundle(configuration); - bl.executePlugin(testID, bundle); - let report = bundle.getReport(); - - it("should create a report object with valid schema", function() { - - let formatter = bl.getFormatter("json.js"); - if (!formatter) { - assert.fail("formatter implementation not defined"); - } - let schema = require("./../fixtures/reportSchema.js"), - Validator = require("jsonschema").Validator, - v = new Validator(), - jsonReport = JSON.parse(formatter(report)), - validationResult = v.validate(jsonReport, schema); - assert.equal( - validationResult.errors.length, - 0, - validationResult.errors - ); - }); - -}); diff --git a/test/specs/ST003-ev-json-condition.js b/test/specs/ST003-ev-json-condition.js new file mode 100644 index 00000000..dca1d476 --- /dev/null +++ b/test/specs/ST003-ev-json-condition.js @@ -0,0 +1,92 @@ +/* + Copyright 2019-2022 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. +*/ + +/* global describe, it */ + +const assert = require("assert"), + ruleId = 'ST003', + path = require("path"), + debug = require("debug")(`apigeelint:${ruleId}`), + util = require("util"), + bl = require("../../lib/package/bundleLinter.js"); + +const expectedMessageRe = + new RegExp("^For the ExtractVariables step \\([-A-Za-z0-9_]{2,}\\), an appropriate check for a message body was not found\\..*"); + +describe(`${ruleId} - ExtractVariables JSONPayload Conditions`, () => { + function check(suffix, bundleType, expected) { + let configuration = { + debug: true, + source: { + type: "filesystem", + path: path.resolve(__dirname, `../fixtures/resources/ExtractVariables-Attachment/${bundleType}`), + bundleType + }, + excluded: {}, + setExitCode: false, + output: () => {} // suppress output + }; + + bl.lint(configuration, (bundle) => { + let items = bundle.getReport(); + assert.ok(items); + assert.ok(items.length); + debug(util.format(items)); + let sfItems = items.filter( m => m.filePath.endsWith(suffix)); + debug(util.format(sfItems)); + assert.equal(sfItems.length, 1); + sfItems.forEach( item => + debug(util.format(item.messages))); + let st003Messages = sfItems[0].messages.filter( m => m.ruleId == ruleId); + debug(util.format(st003Messages)); + assert.equal(st003Messages.length, expected.length); + + expected.forEach( (item, ix) => { + assert.equal(st003Messages[ix].line, item.line, `case(${ix}) line`); + assert.equal(st003Messages[ix].column, item.column, `case(${ix}) column`); + assert.equal(st003Messages[ix].severity, 1, `case(${ix}) severity`); + assert.ok(st003Messages[ix].message.match(expectedMessageRe), `case(${ix}) message`); + }); + }); + } + + it('should find the expected warnings in an apiproxy', () => { + let expected = [ + { + line: 100, + column: 9 + }, + { + line: 112, + column: 9 + }, + { + line: 125, + column: 9 + } + ]; + + check('endpoint1.xml', 'apiproxy', expected); + }); + + it('should find the expected warnings in a sharedflow', () => { + let expected = [ + { line: 5, column: 3 } + ]; + check('sf-default.xml', 'sharedflowbundle', expected); + }); + +}); diff --git a/test/specs/ST004-ev-xml-condition.js b/test/specs/ST004-ev-xml-condition.js new file mode 100644 index 00000000..257cf5c7 --- /dev/null +++ b/test/specs/ST004-ev-xml-condition.js @@ -0,0 +1,104 @@ +/* + Copyright 2019-2022 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. +*/ + +/* global describe, it */ + +const assert = require("assert"), + ruleId = 'ST004', + path = require("path"), + debug = require("debug")(`apigeelint:${ruleId}`), + util = require("util"), + bl = require("../../lib/package/bundleLinter.js"); + +const expectedMessageRe = + new RegExp("^For the ExtractVariables step \\([-A-Za-z0-9_]{2,}\\), an appropriate check for a message body was not found\\..*"); + +describe(`${ruleId} - ExtractVariables XMLPayload Conditions`, () => { + function check(suffix, bundleType, expected) { + let configuration = { + debug: true, + source: { + type: "filesystem", + path: path.resolve(__dirname, `../fixtures/resources/ExtractVariables-Attachment/${bundleType}`), + bundleType + }, + excluded: {}, + setExitCode: false, + output: () => {} // suppress output + }; + + bl.lint(configuration, (bundle) => { + let items = bundle.getReport(); + assert.ok(items); + assert.ok(items.length); + debug('all items: ' + util.format(items)); + let itemsForFileOfInterest = items.filter( m => m.filePath.endsWith(suffix)); + debug('items for that filepath: ' + util.format(itemsForFileOfInterest)); + assert.equal(itemsForFileOfInterest.length, 1); + // itemsForFileOfInterest.forEach( item => + // debug(util.format(item.messages))); + let st004Messages = itemsForFileOfInterest[0].messages.filter( m => m.ruleId == ruleId); + + debug(`ST004 messages (${st004Messages.length}): ` + util.format(st004Messages)); + assert.equal(st004Messages.length, expected.length); + + expected.forEach( (item, ix) => { + assert.equal(st004Messages[ix].line, item.line, `case(${ix}) line`); + assert.equal(st004Messages[ix].column, item.column, `case(${ix}) column`); + assert.equal(st004Messages[ix].severity, 1, `case(${ix}) severity`); + assert.ok(st004Messages[ix].message.match(expectedMessageRe), `case(${ix}) message`); + }); + }); + } + + it('should generate the expected warnings in an apiproxy', () => { + let expected = [ + { + line: 29, + column: 7 + }, + { + line: 38, + column: 7 + }, + { + line: 164, + column: 9 + }, + { + line: 176, + column: 9 + }, + { + line: 189, + column: 9 + }, + { + line: 240, + column: 9 + } + ]; + check('endpoint1.xml', 'apiproxy', expected); + }); + + it('should find the expected warnings in a sharedflow', () => { + let expected = [ + { line: 12, column: 3 } + ]; + check('sf-default.xml', 'sharedflowbundle', expected); + }); + +}); diff --git a/test/specs/ST005-ev-form-condition.js b/test/specs/ST005-ev-form-condition.js new file mode 100644 index 00000000..9110a702 --- /dev/null +++ b/test/specs/ST005-ev-form-condition.js @@ -0,0 +1,100 @@ +/* + Copyright 2019-2022 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. +*/ + +/* global describe, it */ + +const assert = require("assert"), + ruleId = 'ST005', + path = require("path"), + debug = require("debug")(`apigeelint:${ruleId}`), + util = require("util"), + bl = require("../../lib/package/bundleLinter.js"); + +const expectedMessageRe = + new RegExp("For the ExtractVariables step \\([-A-Za-z0-9_]{2,}\\), an appropriate check for a message body was not found."); + +describe(`${ruleId} - ExtractVariables XMLPayload Conditions`, () => { + function check(suffix, bundleType, expected) { + let configuration = { + debug: true, + source: { + type: "filesystem", + path: path.resolve(__dirname, `../fixtures/resources/ExtractVariables-Attachment/${bundleType}`), + bundleType + }, + excluded: {}, + setExitCode: false, + output: () => {} // suppress output + }; + + bl.lint(configuration, (bundle) => { + let items = bundle.getReport(); + assert.ok(items); + assert.ok(items.length); + debug('all items: ' + util.format(items)); + let itemsForFileOfInterest = items.filter( m => m.filePath.endsWith(suffix)); + debug('items for that filepath: ' + util.format(itemsForFileOfInterest)); + assert.equal(itemsForFileOfInterest.length, 1); + + itemsForFileOfInterest.forEach( (item, ix) => + debug(`item ${ix}: ` + util.format(item.messages))); + + let st005Messages = itemsForFileOfInterest[0].messages.filter( m => m.ruleId == ruleId); + debug(`ST005 messages (${st005Messages.length}): ` + util.format(st005Messages)); + assert.equal(st005Messages.length, expected.length); + expected.forEach( (item, ix) => { + assert.equal(st005Messages[ix].line, item.line, `case(${ix}) line`); + assert.equal(st005Messages[ix].column, item.column, `case(${ix}) column`); + assert.equal(st005Messages[ix].severity, 1, `case(${ix}) severity`); + assert.ok(st005Messages[ix].message.match(expectedMessageRe), `case(${ix}) message`); + }); + + }); + } + + it('should generate the expected warnings in an apiproxy', () => { + let expected = [ + { + line: 48, + column: 7 + }, + { + line: 54, + column: 7 + }, + { + line: 72, + column: 7 + }, + { + line: 254, + column: 9 + } + ]; + check('endpoint1.xml', "apiproxy", expected); + }); + + it('should generate the expected warnings in a sharedflow', () => { + let expected = [ + { + line: 36, + column: 3 + } + ]; + check('sf-default.xml', "sharedflowbundle", expected); + }); + +}); diff --git a/test/specs/ST006-jtp-condition.js b/test/specs/ST006-jtp-condition.js new file mode 100644 index 00000000..19e4afa7 --- /dev/null +++ b/test/specs/ST006-jtp-condition.js @@ -0,0 +1,88 @@ +/* + Copyright 2019-2022 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. +*/ + +/* global describe, it */ + +const assert = require("assert"), + ruleId = 'ST006', + path = require("path"), + debug = require("debug")(`apigeelint:${ruleId}`), + util = require("util"), + bl = require("../../lib/package/bundleLinter.js"); + +const expectedMessageRe = + new RegExp("^For the [A-Za-z]{4,} step \\([-A-Za-z0-9_]{2,}\\), an appropriate check for a message body was not found\\..*"); + +describe(`${ruleId} - JSONThreatProtection Conditions`, () => { + function check(suffix, bundleType, expected) { + let configuration = { + debug: true, + source: { + type: "filesystem", + path: path.resolve(__dirname, `../fixtures/resources/ThreatProtection-Attachment/${bundleType}`), + bundleType + }, + excluded: {}, + setExitCode: false, + output: () => {} // suppress output + }; + + bl.lint(configuration, (bundle) => { + let items = bundle.getReport(); + assert.ok(items); + assert.ok(items.length); + debug(util.format(items)); + let proxyEndpointItems = items.filter( m => m.filePath.endsWith(suffix)); + debug(util.format(proxyEndpointItems)); + assert.equal(proxyEndpointItems.length, 1); + proxyEndpointItems.forEach( item => + debug(util.format(item.messages))); + let st006Messages = proxyEndpointItems[0].messages.filter( m => m.ruleId == ruleId); + + debug(util.format(st006Messages)); + assert.equal(st006Messages.length, expected.length); + + expected.forEach( (item, ix) => { + assert.equal(st006Messages[ix].line, item.line, `case(${ix}) line`); + assert.equal(st006Messages[ix].column, item.column, `case(${ix}) column`); + assert.equal(st006Messages[ix].severity, 1, `case(${ix}) severity`); + assert.ok(st006Messages[ix].message.match(expectedMessageRe), `case(${ix}) message`); + }); + + }); + } + + it('should generate the expected warnings in an apiproxy', () => { + let expected = [ + { + line: 36, + column: 9 + } + ]; + check('endpoint1.xml', 'apiproxy', expected); + }); + + it('should generate the expected warnings in a sharedflow', () => { + let expected = [ + { + line: 4, + column: 3 + } + ]; + check('sf-default.xml', 'sharedflowbundle', expected); + }); + +}); diff --git a/test/specs/ST007-xtp-condition.js b/test/specs/ST007-xtp-condition.js new file mode 100644 index 00000000..409d81e5 --- /dev/null +++ b/test/specs/ST007-xtp-condition.js @@ -0,0 +1,93 @@ +/* + Copyright 2019-2022 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. +*/ + +/* global describe, it */ + +const assert = require("assert"), + ruleId = 'ST007', + path = require("path"), + debug = require("debug")(`apigeelint:${ruleId}`), + util = require("util"), + bl = require("../../lib/package/bundleLinter.js"); + +const expectedMessageRe = + new RegExp("^For the [A-Za-z]{4,} step \\([-A-Za-z0-9_]{2,}\\), an appropriate check for a message body was not found\\..*"); + + +describe(`${ruleId} - XMLThreatProtection Conditions`, () => { + function check(suffix, bundleType, expected) { + let configuration = { + debug: true, + source: { + type: "filesystem", + path: path.resolve(__dirname, `../fixtures/resources/ThreatProtection-Attachment/${bundleType}`), + bundleType + }, + excluded: {}, + setExitCode: false, + output: () => {} // suppress output + }; + + bl.lint(configuration, (bundle) => { + let items = bundle.getReport(); + assert.ok(items); + assert.ok(items.length); + debug(util.format(items)); + let proxyEndpointItems = items.filter( m => m.filePath.endsWith(suffix)); + debug(util.format(proxyEndpointItems)); + assert.equal(proxyEndpointItems.length, 1); + proxyEndpointItems.forEach( item => + debug(util.format(item.messages))); + let st007Messages = proxyEndpointItems[0].messages.filter( m => m.ruleId == ruleId); + + debug(util.format(st007Messages)); + assert.equal(st007Messages.length, expected.length); + + expected.forEach( (item, ix) => { + assert.equal(st007Messages[ix].line, item.line, `case(${ix}) line`); + assert.equal(st007Messages[ix].column, item.column, `case(${ix}) column`); + assert.equal(st007Messages[ix].severity, 1, `case(${ix}) severity`); + assert.ok(st007Messages[ix].message.match(expectedMessageRe), `case(${ix}) message: ${st007Messages[ix].message}`); + }); + + }); + } + + it('should generate the expected warnings in an apiproxy', () => { + let expected = [ + { + line: 59, + column: 9 + }, + { + line: 94, + column: 9 + } + ]; + check('endpoint1.xml', 'apiproxy', expected) + }); + + it('should generate the expected warnings in a sharedflow', () => { + let expected = [ + { + line: 10, + column: 3 + } + ]; + check('sf-default.xml', 'sharedflowbundle', expected) + }); + +});