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