UNPKG

@ping-identity/dvlint-base-rule-pack

Version:
128 lines (110 loc) 6.14 kB
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;