This is an automated email from the ASF dual-hosted git repository. dhavaljrajpara pushed a commit to branch RANGER-5860 in repository https://gitbox.apache.org/repos/asf/ranger.git
commit 313f0d40b87ed6ce74037255c57bbb6b9d996d1f Author: Dhaval Rajpara <[email protected]> AuthorDate: Wed Oct 28 16:42:31 2026 +0530 RANGER-5852 : Added access review and submit form logic to contraint policy form --- .../main/webapp/react-webapp/src/utils/XAUtils.js | 76 +++++ .../NewPolicyForm/CreateNewPolicyForm.jsx | 331 +++++++++++++-------- .../ReviewConfirmStep.jsx | 118 +++++++- .../NewPolicyForm/policyFormValidation.js | 106 ++++++- 4 files changed, 490 insertions(+), 141 deletions(-) diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js b/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js index 89b3916a3..9d4c2e5bf 100644 --- a/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js +++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAUtils.js @@ -1993,6 +1993,82 @@ export const prunePolicyItemAccessesToAllowedTypes = ( } }; +/** + * When resources change, drop constraint-specific fields on rules whose + * constraint is no longer matched; prune accesses on rules that remain valid. + */ +export const pruneConstraintPolicyItemsToMatchedResources = ( + form, + selectedServiceComponentDef +) => { + const formValues = form.getState().values; + if ( + formValues?.policyType !== + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value + ) { + return; + } + + const attrName = "constraintPolicyItems"; + const items = formValues?.[attrName]; + if (!Array.isArray(items)) { + return; + } + + const matchedDefs = getMatchedConstraintDefs( + selectedServiceComponentDef, + formValues + ); + const allowedConstraintNames = new Set( + (matchedDefs || []).map((def) => def.name).filter(Boolean) + ); + + for (let i = 0; i < items.length; i++) { + const item = items[i]; + if (!item || typeof item !== "object") { + continue; + } + + const constraintName = getConstraintNameFromField( + item.constraintInfo?.constraintName + ); + if (!constraintName) { + continue; + } + + if (!allowedConstraintNames.has(constraintName)) { + form.change(`${attrName}[${i}].constraintInfo.constraintName`, undefined); + form.change(`${attrName}[${i}].constraintInfo.parameters`, undefined); + form.change(`${attrName}[${i}].accesses`, undefined); + continue; + } + + const constraintDef = getConstraintDefByName( + selectedServiceComponentDef, + constraintName + ); + const allowedAccessTypes = new Set( + ( + getAccessTypesForConstraintDef( + selectedServiceComponentDef, + constraintDef + ) || [] + ).map((at) => at.name) + ); + + if (!Array.isArray(item.accesses) || allowedAccessTypes.size === 0) { + continue; + } + + const prunedAccesses = item.accesses.filter( + (a) => a?.type && allowedAccessTypes.has(a.type) + ); + if (prunedAccesses.length !== item.accesses.length) { + form.change(`${attrName}[${i}].accesses`, prunedAccesses); + } + } +}; + //CommonFunction to get display label for policy permission item export const getPolicyPermissionItemDisplayLbl = ( serviceDef, diff --git a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/CreateNewPolicyForm.jsx b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/CreateNewPolicyForm.jsx index 97ff12b19..e41c48cac 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/CreateNewPolicyForm.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/CreateNewPolicyForm.jsx @@ -25,7 +25,9 @@ import { fetchApi } from "Utils/fetchAPI"; import { getResourcesDefVal, prunePolicyItemAccessesToAllowedTypes, - policyConditionUpdatedJSON + pruneConstraintPolicyItemsToMatchedResources, + policyConditionUpdatedJSON, + getConstraintNameFromField } from "Utils/XAUtils"; import { buildActionReqsMapFromConditionDef, @@ -40,7 +42,8 @@ import { RangerPolicyType } from "Utils/XAEnums"; import { validatePolicyItems, validatePolicyDetails, - hasMissingAccessPermissionOnPolicyItems + hasMissingAccessPermissionOnPolicyItems, + hasMissingConstraintFieldsOnPolicyItems } from "./policyFormValidation"; import ServiceStep from "./PolicyCreateStepperStepComp/ServiceStep"; import ResourcesStep from "./PolicyCreateStepperStepComp/ResourcesStep"; @@ -68,6 +71,154 @@ const CreateNewPolicyForm = () => { } }; + const principalName = (p) => { + if (typeof p !== "object") return p; + if (p.name) return p.name; + if (p.value) { + const colonIdx = p.value.indexOf(":"); + return colonIdx !== -1 ? p.value.slice(colonIdx + 1) : p.value; + } + return p; + }; + + const appendAccessesPrincipalsAndConditions = (obj, key) => { + if (key?.accesses) { + if (key.accesses.tableList && isArray(key.accesses.tableList)) { + obj.accesses = key.accesses.tableList.flatMap((tableItem) => { + if (tableItem.permission && isArray(tableItem.permission)) { + return tableItem.permission.map((perm) => ({ + type: perm, + isAllowed: true + })); + } + return []; + }); + } else if (isArray(key.accesses) && key.accesses.length > 0) { + obj.accesses = key.accesses.map((access) => { + if (access.type && access.isAllowed !== undefined) { + return { + type: access.type, + isAllowed: access.isAllowed + }; + } + return { + type: + typeof access === "object" && access.value + ? access.value + : access.type || access, + isAllowed: true + }; + }); + } + } + + if (key.users) { + if (isArray(key.users)) { + obj.users = key.users.map(principalName); + } else if (key.users.users && isArray(key.users.users)) { + obj.users = key.users.users.map(principalName); + } + } + + if ( + key.users?.groups && + isArray(key.users.groups) && + key.users.groups.length > 0 + ) { + obj.groups = key.users.groups.map(principalName); + } else if (key.groups && isArray(key.groups) && key.groups.length > 0) { + obj.groups = key.groups.map(principalName); + } + + if ( + key.users?.roles && + isArray(key.users.roles) && + key.users.roles.length > 0 + ) { + obj.roles = key.users.roles.map(principalName); + } else if (key.roles && isArray(key.roles) && key.roles.length > 0) { + obj.roles = key.roles.map(principalName); + } + + if (key?.conditions && !isEmpty(key.conditions)) { + obj.conditions = []; + Object.entries(key.conditions).forEach( + ([conditionKey, conditionValue]) => { + if (conditionValue != "" && conditionValue != null) { + obj.conditions.push({ + type: conditionKey, + values: isArray(conditionValue) + ? conditionValue.map((m) => + typeof m === "object" && m.value ? m.value : m + ) + : [conditionValue] + }); + } + } + ); + } + }; + + const buildConstraintInfoForApi = (constraintInfoForm) => { + if (!constraintInfoForm) { + return null; + } + const constraintName = getConstraintNameFromField( + constraintInfoForm.constraintName + ); + if (!constraintName) { + return null; + } + + const info = { constraintName }; + const rawParams = constraintInfoForm.parameters; + if (rawParams && typeof rawParams === "object") { + const parameters = {}; + Object.entries(rawParams).forEach(([paramKey, paramVal]) => { + if (paramVal === "" || paramVal == null) { + return; + } + parameters[paramKey] = + typeof paramVal === "object" && paramVal.value != null + ? paramVal.value + : String(paramVal); + }); + if (!isEmpty(parameters)) { + info.parameters = parameters; + } + } + return info; + }; + + /** Constraint policy (type 4) — API shape for constraintPolicyItems */ + const getConstraintPolicyItemsVal = (formData) => { + const items = formData?.constraintPolicyItems; + if (!items || items.length === 0) { + return []; + } + + const policyResourceItem = []; + for (const key of items) { + if (isEmpty(key) || Object.entries(key).length === 0) { + continue; + } + + const obj = {}; + appendAccessesPrincipalsAndConditions(obj, key); + + const constraintInfo = buildConstraintInfoForApi(key.constraintInfo); + if (constraintInfo) { + obj.constraintInfo = constraintInfo; + } + + if (!isEmpty(obj)) { + policyResourceItem.push(obj); + } + } + + return policyResourceItem; + }; + // Transform policy items to API format const getPolicyItemsVal = (formData, name) => { if (!formData[name] || formData[name].length === 0) { @@ -92,85 +243,7 @@ const CreateNewPolicyForm = () => { } } - // Accesses (permissions) - handle both regular and tag-based - if (key?.accesses) { - // For tag-based policies with tableList structure - if (key.accesses.tableList && isArray(key.accesses.tableList)) { - obj.accesses = key.accesses.tableList.flatMap((tableItem) => { - if (tableItem.permission && isArray(tableItem.permission)) { - return tableItem.permission.map((perm) => ({ - type: perm, - isAllowed: true - })); - } - return []; - }); - } - // For regular policies with array of accesses - else if (isArray(key.accesses) && key.accesses.length > 0) { - obj.accesses = key.accesses.map((access) => { - // Handle if access is already in correct format {type: "read", isAllowed: true} - if (access.type && access.isAllowed !== undefined) { - return { - type: access.type, - isAllowed: access.isAllowed - }; - } - // Handle if access is object with value property - return { - type: - typeof access === "object" && access.value - ? access.value - : access.type || access, - isAllowed: true - }; - }); - } - } - - // Resolve a stored principal object to its plain name string. - // Items are stored as { value: "USER:admin", name: "admin", type: "USER" }. - // We always prefer .name; fall back to stripping the "TYPE:" prefix from .value. - const principalName = (p) => { - if (typeof p !== "object") return p; - if (p.name) return p.name; - if (p.value) { - const colonIdx = p.value.indexOf(":"); - return colonIdx !== -1 ? p.value.slice(colonIdx + 1) : p.value; - } - return p; - }; - - // Users - if (key.users) { - if (isArray(key.users)) { - obj.users = key.users.map(principalName); - } else if (key.users.users && isArray(key.users.users)) { - obj.users = key.users.users.map(principalName); - } - } - - // Groups - if ( - key.users?.groups && - isArray(key.users.groups) && - key.users.groups.length > 0 - ) { - obj.groups = key.users.groups.map(principalName); - } else if (key.groups && isArray(key.groups) && key.groups.length > 0) { - obj.groups = key.groups.map(principalName); - } - - // Roles - if ( - key.users?.roles && - isArray(key.users.roles) && - key.users.roles.length > 0 - ) { - obj.roles = key.users.roles.map(principalName); - } else if (key.roles && isArray(key.roles) && key.roles.length > 0) { - obj.roles = key.roles.map(principalName); - } + appendAccessesPrincipalsAndConditions(obj, key); // Row Filter Info (for row filter policies) if ( @@ -179,14 +252,12 @@ const CreateNewPolicyForm = () => { key.rowFilterInfo !== "" ) { obj.rowFilterInfo = {}; - // If it's already an object with filterExpr if ( typeof key.rowFilterInfo === "object" && key.rowFilterInfo.filterExpr ) { obj.rowFilterInfo.filterExpr = key.rowFilterInfo.filterExpr; } else { - // If it's a string, use it directly as filterExpr obj.rowFilterInfo.filterExpr = key.rowFilterInfo; } } @@ -198,42 +269,17 @@ const CreateNewPolicyForm = () => { !isEmpty(key.dataMaskInfo) ) { obj.dataMaskInfo = {}; - - // Handle if dataMaskInfo is an object with value property if (typeof key.dataMaskInfo === "object") { obj.dataMaskInfo.dataMaskType = key.dataMaskInfo.value || key.dataMaskInfo.dataMaskType; - - // Include valueExpr for custom masking if (key.dataMaskInfo.valueExpr) { obj.dataMaskInfo.valueExpr = key.dataMaskInfo.valueExpr; } } else { - // Handle if dataMaskInfo is a string obj.dataMaskInfo.dataMaskType = key.dataMaskInfo; } } - // Conditions (rule-level conditions) - if (key?.conditions && !isEmpty(key.conditions)) { - obj.conditions = []; - Object.entries(key.conditions).map( - ([conditionKey, conditionValue]) => { - if (conditionValue != "" && conditionValue != null) { - return obj.conditions.push({ - type: conditionKey, - values: isArray(conditionValue) - ? conditionValue.map((m) => - typeof m === "object" && m.value ? m.value : m - ) - : [conditionValue] - }); - } - return null; - } - ); - } - policyResourceItem.push(obj); } } @@ -248,32 +294,53 @@ const CreateNewPolicyForm = () => { ); return; } + if ( + hasMissingConstraintFieldsOnPolicyItems( + values, + selectedServiceComponentDef + ) + ) { + toast.error( + "Please complete each constraint rule in the Access Rules step: select a constraint name, choose permissions, and fill all required parameters." + ); + return; + } try { setBlockUI(true); let data = {}; - - // Policy Items (Allow Conditions) - data.policyItems = getPolicyItemsVal(values, "policyItems"); - data.allowExceptions = getPolicyItemsVal(values, "allowExceptions"); - - // Deny Policy Items - if (values?.isDenyAllElse) { + const isConstraintPolicy = + values.policyType === + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value; + + if (isConstraintPolicy) { + data.constraintPolicyItems = getConstraintPolicyItemsVal(values); + data.policyItems = []; + data.allowExceptions = []; data.denyPolicyItems = []; data.denyExceptions = []; + data.dataMaskPolicyItems = []; + data.rowFilterPolicyItems = []; } else { - data.denyPolicyItems = getPolicyItemsVal(values, "denyPolicyItems"); - data.denyExceptions = getPolicyItemsVal(values, "denyExceptions"); - } + data.policyItems = getPolicyItemsVal(values, "policyItems"); + data.allowExceptions = getPolicyItemsVal(values, "allowExceptions"); - // Masking and Row Filter Policy Items - data.dataMaskPolicyItems = getPolicyItemsVal( - values, - "dataMaskPolicyItems" - ); - data.rowFilterPolicyItems = getPolicyItemsVal( - values, - "rowFilterPolicyItems" - ); + if (values?.isDenyAllElse) { + data.denyPolicyItems = []; + data.denyExceptions = []; + } else { + data.denyPolicyItems = getPolicyItemsVal(values, "denyPolicyItems"); + data.denyExceptions = getPolicyItemsVal(values, "denyExceptions"); + } + + data.dataMaskPolicyItems = getPolicyItemsVal( + values, + "dataMaskPolicyItems" + ); + data.rowFilterPolicyItems = getPolicyItemsVal( + values, + "rowFilterPolicyItems" + ); + } // Basic policy fields data.name = values.policyName; @@ -429,6 +496,9 @@ const CreateNewPolicyForm = () => { localStorage.removeItem("zoneDetails"); } + // eslint-disable-next-line no-console + console.log("Create policy payload:", JSON.stringify(data, null, 2)); + const getPolicyCount = await fetchApi({ url: "plugins/policies/count", method: "GET", @@ -591,7 +661,8 @@ const CreateNewPolicyForm = () => { selectedServiceComponentDef={selectedServiceComponentDef} /> ), - validate: validatePolicyItems + validate: (values) => + validatePolicyItems(values, selectedServiceComponentDef) }, { title: "Policy Details", @@ -634,6 +705,10 @@ const CreateNewPolicyForm = () => { form, selectedServiceComponentDef ); + pruneConstraintPolicyItemsToMatchedResources( + form, + selectedServiceComponentDef + ); const conditionDefVal = policyConditionUpdatedJSON( selectedServiceComponentDef?.policyConditions diff --git a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ReviewConfirmStep.jsx b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ReviewConfirmStep.jsx index 8f555c5dc..55728e4a8 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ReviewConfirmStep.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ReviewConfirmStep.jsx @@ -23,7 +23,10 @@ import { isEmpty, isArray, find, map } from "lodash"; import moment from "moment"; import { getResourcesDefVal, - getPolicyConditionDisplayLbl + getPolicyConditionDisplayLbl, + getConstraintNameFromField, + getConstraintDefByName, + getAccessTypesForConstraintDef } from "Utils/XAUtils"; import { DefStatus, RangerPolicyType } from "Utils/XAEnums"; import userColourIcon from "Images/user-colour.svg"; @@ -91,6 +94,8 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { policyType == RangerPolicyType.RANGER_MASKING_POLICY_TYPE.value; const isRowFilter = policyType == RangerPolicyType.RANGER_ROW_FILTER_POLICY_TYPE.value; + const isConstraint = + policyType == RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value; const enableDenyAndExceptions = selectedServiceComponentDef?.options?.enableDenyAndExceptionsInPolicies === "true"; @@ -99,14 +104,25 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { ? "Masking" : isRowFilter ? "Row Filter" - : "Access"; + : isConstraint + ? RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.label + : "Access"; - const displayTagPermissionItem = (permission) => { + const displayTagPermissionItem = (permission, constraintName) => { let srcOp = []; if (isMasking) { srcOp = selectedServiceComponentDef.dataMaskDef?.accessTypes || []; } else if (isRowFilter) { srcOp = selectedServiceComponentDef.rowFilterDef?.accessTypes || []; + } else if (isConstraint && constraintName) { + const constraintDef = getConstraintDefByName( + selectedServiceComponentDef, + constraintName + ); + srcOp = getAccessTypesForConstraintDef( + selectedServiceComponentDef, + constraintDef + ); } else { srcOp = selectedServiceComponentDef.accessTypes; } @@ -245,7 +261,25 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { const hasRowFilter = !!( item.rowFilterInfo && String(item.rowFilterInfo).trim() ); - const hasDelegateAdmin = !isMasking && !isRowFilter && !isTagAccesses; + const constraintName = getConstraintNameFromField( + item.constraintInfo?.constraintName + ); + const constraintDef = constraintName + ? getConstraintDefByName(selectedServiceComponentDef, constraintName) + : null; + const constraintParams = item.constraintInfo?.parameters; + const hasConstraintParams = + constraintParams && + typeof constraintParams === "object" && + Object.keys(constraintParams).some( + (k) => + constraintParams[k] !== "" && + constraintParams[k] != null && + constraintParams[k] !== undefined + ); + const hasConstraintName = Boolean(constraintName); + const hasDelegateAdmin = + !isMasking && !isRowFilter && !isConstraint && !isTagAccesses; if ( !hasPrincipal && @@ -253,6 +287,8 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { !hasConditions && !hasDataMask && !hasRowFilter && + !hasConstraintName && + !hasConstraintParams && !hasDelegateAdmin ) { return null; @@ -269,6 +305,14 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { {/* rule body */} <div className="px-3 py-2"> + {isConstraint && hasConstraintName && ( + <InfoRow label="Constraint Name"> + <Badge bg="info"> + {constraintDef?.label || constraintName} + </Badge> + </InfoRow> + )} + {hasAccesses && ( <InfoRow label="Permissions"> {isTagAccesses ? ( @@ -288,7 +332,10 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { <td> <div className="d-flex flex-wrap gap-1"> {( - displayTagPermissionItem(t.permission || []) || [] + displayTagPermissionItem( + t.permission || [], + constraintName + ) || [] ).map((p, pi) => ( <Badge key={pi} bg="primary"> {p} @@ -381,13 +428,43 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { {/* Row filter - ONLY SHOW IF HAS ROW FILTER */} {hasRowFilter && ( - <InfoRow label="Row Level Filter" isLast={true}> + <InfoRow label="Row Level Filter" isLast={!hasConstraintParams}> <code className="bg-light border rounded px-2 py-1 small d-inline-block"> {String(item.rowFilterInfo)} </code> </InfoRow> )} + {isConstraint && hasConstraintParams && ( + <InfoRow label="Parameters" isLast={true}> + <Table bordered size="sm" className="mb-0 align-middle"> + <thead className="table-light"> + <tr> + <th className="small p-2">Parameter</th> + <th className="small p-2">Value</th> + </tr> + </thead> + <tbody> + {Object.keys(constraintParams).map((paramKey) => { + const paramVal = constraintParams[paramKey]; + if (paramVal === "" || paramVal == null) return null; + const paramDef = find(constraintDef?.parameters || [], { + name: paramKey + }); + return ( + <tr key={paramKey}> + <td style={{ minWidth: "210px" }}> + {paramDef?.label || paramKey} + </td> + <td className="text-word-break">{String(paramVal)}</td> + </tr> + ); + })} + </tbody> + </Table> + </InfoRow> + )} + {/* Delegate Admin - ONLY SHOW IF HAS DELEGATE ADMIN */} {hasDelegateAdmin && ( <InfoRow label="Delegate Admin" isLast={true}> @@ -446,6 +523,13 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { return ( <RuleGroup attrName="rowFilterPolicyItems" title="Row Filter Rules" /> ); + if (isConstraint) + return ( + <RuleGroup + attrName="constraintPolicyItems" + title="Constraint Rules" + /> + ); return ( <> @@ -584,7 +668,15 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { </InfoRow> <InfoRow label="Policy Type" isLast={true}> <Badge - bg={isMasking ? "warning" : isRowFilter ? "info" : "primary"} + bg={ + isMasking + ? "warning" + : isRowFilter + ? "info" + : isConstraint + ? "secondary" + : "primary" + } text={isMasking ? "dark" : undefined} > {policyTypeLabel} @@ -604,7 +696,17 @@ const ReviewConfirmStep = ({ values, selectedServiceComponentDef }) => { {/* Step 3 — Access Rules */} <Accordion.Item eventKey="3"> <Accordion.Header> - <SectionHeader title="Access Rules" /> + <SectionHeader + title={ + isMasking + ? "Mask Rules" + : isRowFilter + ? "Row Filter Rules" + : isConstraint + ? "Constraint Rules" + : "Access Rules" + } + /> </Accordion.Header> <Accordion.Body>{renderAccessRules()}</Accordion.Body> </Accordion.Item> diff --git a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/policyFormValidation.js b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/policyFormValidation.js index 91bfa331b..fd0cf1c72 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/policyFormValidation.js +++ b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/policyFormValidation.js @@ -18,6 +18,10 @@ */ import { isEmpty, isArray } from "lodash"; +import { + getConstraintNameFromField, + getConstraintDefByName +} from "Utils/XAUtils"; export const validatePolicyName = (policyName) => { if (!policyName || policyName.trim() === "") { @@ -117,7 +121,61 @@ const validateRowFilterPolicyItem = (item) => { return ruleErrors; }; -export const validateSinglePolicyItem = (item, policyType) => { +const hasConstraintNameInItem = (item) => + Boolean(getConstraintNameFromField(item.constraintInfo?.constraintName)); + +const hasMissingMandatoryConstraintParameters = (item, serviceDef) => { + if (!serviceDef) { + return false; + } + const constraintName = getConstraintNameFromField( + item.constraintInfo?.constraintName + ); + if (!constraintName) { + return false; + } + const def = getConstraintDefByName(serviceDef, constraintName); + const parameters = item.constraintInfo?.parameters || {}; + for (const param of def?.parameters || []) { + if (param.mandatory) { + const val = parameters[param.name]; + if (val === "" || val == null) { + return true; + } + } + } + return false; +}; + +const validateConstraintPolicyItem = (item, serviceDef) => { + const ruleErrors = {}; + const principals = hasPrincipals(item); + const accesses = hasAccessesInItem(item); + const constraintName = hasConstraintNameInItem(item); + const missingMandatoryParams = hasMissingMandatoryConstraintParameters( + item, + serviceDef + ); + + if (principals && !constraintName) { + ruleErrors.constraintName = + "Please select a constraint name for this policy"; + } + if (principals && constraintName && !accesses) { + ruleErrors.accesses = "Please select permission for this policy"; + } + if (principals && constraintName && missingMandatoryParams) { + ruleErrors.parameters = + "Please provide all required parameters for this constraint"; + } + if ((accesses || constraintName) && !principals) { + ruleErrors.users = "Please select users/groups/roles for this policy"; + } + + return ruleErrors; +}; + +export const validateSinglePolicyItem = (item, policyType, serviceDef) => { if (!item || isEmpty(item)) { return {}; } @@ -127,6 +185,8 @@ export const validateSinglePolicyItem = (item, policyType) => { return validateDataMaskPolicyItem(item); case 2: return validateRowFilterPolicyItem(item); + case 4: + return validateConstraintPolicyItem(item, serviceDef); case 0: default: return validateAccessPolicyItem(item); @@ -138,6 +198,8 @@ const getArraysToValidate = (policyType) => { return ["dataMaskPolicyItems"]; } else if (policyType == 2) { return ["rowFilterPolicyItems"]; + } else if (policyType == 4) { + return ["constraintPolicyItems"]; } return [ "policyItems", @@ -147,13 +209,17 @@ const getArraysToValidate = (policyType) => { ]; }; -export const validatePolicyItemsArray = (values, policyType) => { +export const validatePolicyItemsArray = (values, policyType, serviceDef) => { const errors = {}; const arraysToValidate = getArraysToValidate(policyType); arraysToValidate.forEach((key) => { (values[key] || []).forEach((item, i) => { - const ruleErrors = validateSinglePolicyItem(item, policyType); + const ruleErrors = validateSinglePolicyItem( + item, + policyType, + serviceDef + ); if (!isEmpty(ruleErrors)) { if (!errors[key]) errors[key] = []; @@ -179,9 +245,9 @@ export const validatePolicyDetails = (values) => { return errors; }; -export const validatePolicyItems = (values) => { +export const validatePolicyItems = (values, serviceDef) => { const policyType = values.policyType ?? 0; - return validatePolicyItemsArray(values, policyType); + return validatePolicyItemsArray(values, policyType, serviceDef); }; /** @@ -209,6 +275,36 @@ export const hasMissingAccessPermissionOnPolicyItems = (values) => { return false; }; +/** + * True when a constraint rule is missing constraint name, permissions, or + * mandatory parameters (e.g. after resources changed). Use on final submit. + */ +export const hasMissingConstraintFieldsOnPolicyItems = (values, serviceDef) => { + if ((values.policyType ?? 0) !== 4) { + return false; + } + const errors = validatePolicyItemsArray(values, 4, serviceDef); + if (isEmpty(errors)) { + return false; + } + const arr = errors.constraintPolicyItems; + if (!isArray(arr)) { + return false; + } + for (let i = 0; i < arr.length; i++) { + const itemError = arr[i]; + if ( + itemError && + (itemError.accesses || + itemError.constraintName || + itemError.parameters) + ) { + return true; + } + } + return false; +}; + export const validatePolicyForm = (values) => ({ ...validatePolicyDetails(values), ...validatePolicyItems(values)
