@ping-identity/dvlint-base-rule-pack
Version:
Collection of base rules used to lint DaVinci flows.
128 lines (110 loc) • 6.14 kB
JavaScript
const { LintRule } = require("@ping-identity/dvlint");
class PingOneFlowRule extends LintRule {
constructor() {
super({
id: "dv-rule-pingOneFlow-001",
description: "Checks for PingOne Flow configurations",
cleans: false,
reference: "",
});
this.addCode("dv-er-pingOneFlow-001", {
description: "Incorrect ending nodes for PingOne flow.",
message: "Incorrect ending nodes for PingOne flow.",
type: "error",
recommendation: "This PingOne-enabled flow is missing appropriate ending nodes. These flows should conclude with either a 'Return Success Response (Redirect Flows)' node or a 'Return Error Response (Redirect Flows)' node of the PingOne Authentication connector."
});
this.addCode("dv-er-pingOneFlow-002", {
description: "Missing false path in capability of PingOne connector.",
message: "Missing '%' path in '%' capability of PingOne connector.",
type: "error",
recommendation: "Define both true and false paths in '%' capability to handle all outcomes."
});
this.addCode("dv-er-pingOneFlow-003", {
description: "Consecutive 'Authenticate via Kerberos' capabilities are not supported",
message: "Consecutive 'Authenticate via Kerberos' capabilities are not supported",
type: "error",
recommendation: "If you want the flow to validate Kerberos credentials against multiple user types, add the applicable user types in the 'Authenticate User via Kerberos' node. Do not place the 'Authenticate User via Kerberos' node back-to-back because Kerberos tokens are one-time-use tokens; chaining one after another will result in failure.",
});
}
runRule() {
try {
const targetFlow = this.mainFlow;
const { nodes, edges } = targetFlow?.graphData?.elements;
const sameCapabilityNodeId = nodes?.filter(n=>n.data.capabilityName === 'kerberosAuthentication').map(d=>d.data.id)
// Incorrect ending nodes for PingOne flow
if (targetFlow?.settings?.pingOneFlow) {
const endNodesCapability = ['returnSuccessResponseRedirect', 'returnErrorResponseRedirect'];
let targetEdgeArr = edges?.filter(d => d.data.target).map(m => m.data.target) || [];
let sourceEdgeArr = edges?.filter(d => d.data.source).map(m => m.data.source) || [];
const onlyInTargetArr = targetEdgeArr.filter(item => !sourceEdgeArr.includes(item)) || [];
const cNameArr = nodes?.filter(n => onlyInTargetArr.includes(n.data.id)).map(m => m.data.capabilityName);
if( !endNodesCapability.some(cap=>cNameArr.includes(cap)) ){
this.addError("dv-er-pingOneFlow-001", {
flowId: targetFlow.flowId,
messageArgs: [targetFlow.name],
});
}
}
// Check for missing false path in capability of pingOneSSOConnector connector
nodes?.forEach((node) => {
if (node.data.connectorId === 'pingOneSSOConnector') {
//target nodes of the current node
const evalNodes = edges?.filter(edge => edge.data.source === node.data.id).map(d => d.data.target) || [];
//case where one evalnode is connected to multiple nodes
let targetFromEvalNodes = [];
if (evalNodes.length === 1) {
targetFromEvalNodes = edges?.filter(edge => edge.data.source === evalNodes[0]).map(d => d.data.target) || [];
}
//connected nodes of the current node
const connectedNodes = nodes?.filter(n => evalNodes.includes(n.data.id)) || [];
let evalNodeArr = [];
connectedNodes.map(cn => {
const propertiesStr = cn.data.properties;
//connectedNodes from current evalNode
const evalNodeTarget = edges?.filter(edge => edge.data.source === cn.data.id).map(d => d.data.target) || [];
if (propertiesStr) {
for (let key in propertiesStr) {
if (evalNodeTarget.includes(key)) {
const value = propertiesStr[key].value;
evalNodeArr.push(value);
}
}
}
});
const trueTriggers = ['allTriggersTrue', 'anyTriggersTrue'];
const falseTriggers = ['allTriggersFalse', 'anyTriggersFalse'];
const hasFalseBranch = evalNodeArr?.some(d => falseTriggers?.includes(d));
const falseBranchCount = evalNodeArr?.filter(d => falseTriggers?.includes(d))?.length;
const hastrueBranch = evalNodeArr?.some(d => trueTriggers?.includes(d)) || targetFromEvalNodes?.length > falseBranchCount || connectedNodes?.length > falseBranchCount;
const noCompleteTriggerBranch = evalNodeArr?.filter(d => d === 'allTriggersComplete' || d === 'always').length === 0;
if (noCompleteTriggerBranch) {
if ((targetFromEvalNodes.length >= 1 || connectedNodes.length >= 1) && (hastrueBranch && !hasFalseBranch) || (hasFalseBranch && !hastrueBranch)) {
const trueOrFalseText = hasFalseBranch && !hastrueBranch ? 'true' : 'false';
this.addError("dv-er-pingOneFlow-002", {
flowId: this.mainFlow.flowId,
recommendationArgs: [node.data.title || node.data.capabilityName],
messageArgs: [trueOrFalseText, node.data.title || node.data.capabilityName],
nodeId: node.data.id,
});
}
}
// Same back to back node kerberos authentication
if (node.data.capabilityName === 'kerberosAuthentication') {
const sourceNode = edges?.filter(e => e.data.source === node.data.id);
edges?.map(e => {
if (sourceNode.length && sourceNode[0].data.target === e.data.source && sameCapabilityNodeId.includes(e.data.target)) {
this.addError("dv-er-pingOneFlow-003", {
flowId: this.mainFlow.flowId,
nodeId: e.data.target,
});
}
})
}
}
})
} catch (err) {
this.addError(undefined, { messageArgs: [`${err}`] });
}
}
}
module.exports = PingOneFlowRule;