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 fe0078bc351f828a932dbbb460a3c017b7fc6a77 Author: Dhaval Rajpara <[email protected]> AuthorDate: Wed Oct 28 16:40:25 2026 +0530 RANGER-5852 : Initial patch for render contraint policy in New Policy Create form --- .../main/webapp/react-webapp/src/utils/XAEnums.js | 6 + .../main/webapp/react-webapp/src/utils/XAUtils.js | 260 +++++++++++++++++++-- .../main/webapp/react-webapp/src/utils/appState.js | 50 +++- .../NewPolicyForm/CreateNewPolicyForm.jsx | 3 +- .../AccessRulesStep.jsx | 6 + .../PolicyPermissionItemVertical.jsx | 211 +++++++++++++++-- .../PolicyCreateStepperStepComp/ServiceStep.jsx | 48 +++- .../src/views/Resources/ResourceComp.jsx | 2 +- 8 files changed, 537 insertions(+), 49 deletions(-) diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js index 5b07e14e8..cfec1cdd8 100755 --- a/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js +++ b/security-admin/src/main/webapp/react-webapp/src/utils/XAEnums.js @@ -551,6 +551,12 @@ export const RangerPolicyType = { label: "Row Level Filter", rbkey: "xa.enum.AccessResult.ACCESS_RESULT_DENIED", tt: "lbl.AccessResult_ACCESS_RESULT_DENIED" + }, + RANGER_CONSTRAINT_POLICY_TYPE: { + value: 4, + label: "Constraint", + rbkey: "xa.enum.AccessResult.ACCESS_RESULT_ALLOWED", + tt: "lbl.AccessResult_ACCESS_RESULT_ALLOWED" } }; 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 c33cb351f..89b3916a3 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 @@ -46,7 +46,11 @@ import { cloneDeep, toUpper, groupBy, - isArray + isArray, + startsWith, + pickBy, + uniqBy, + difference } from "lodash"; import { matchRoutes } from "react-router-dom"; import dateFormat from "dateformat"; @@ -106,6 +110,10 @@ export const isRenderRowFilter = (rowFilterDef) => { : false; }; +export const isRenderConstraint = (constraintDef) => { + return constraintDef && constraintDef.length > 0 ? true : false; +}; + // Returns the policy types (Access / Masking / Row Filter) supported by a service def. // Access is always supported; Masking and Row Filter depend on the service def. export const getSupportedPolicyTypes = (serviceDef) => { @@ -113,13 +121,20 @@ export const getSupportedPolicyTypes = (serviceDef) => { RangerPolicyType.RANGER_ACCESS_POLICY_TYPE.value ]; if (isRenderMasking(serviceDef?.dataMaskDef)) { - supportedPolicyTypes.push(RangerPolicyType.RANGER_MASKING_POLICY_TYPE.value); + supportedPolicyTypes.push( + RangerPolicyType.RANGER_MASKING_POLICY_TYPE.value + ); } if (isRenderRowFilter(serviceDef?.rowFilterDef)) { supportedPolicyTypes.push( RangerPolicyType.RANGER_ROW_FILTER_POLICY_TYPE.value ); } + if (isRenderConstraint(serviceDef?.constraintDef)) { + supportedPolicyTypes.push( + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value + ); + } return supportedPolicyTypes; }; @@ -1519,7 +1534,9 @@ export const requestDataTitle = (serviceType) => { //Policy condition evaluation export const policyConditionUpdatedJSON = (policyCond) => { - let newPolicyConditionJSON = [...(Array.isArray(policyCond) ? policyCond : [])]; + let newPolicyConditionJSON = [ + ...(Array.isArray(policyCond) ? policyCond : []) + ]; newPolicyConditionJSON.filter(function (key) { if (!key?.uiHint || key?.uiHint == "") { if ( @@ -1545,12 +1562,67 @@ export const getResourcesDefVal = (serviceDef, policyType) => { RangerPolicyType.RANGER_ROW_FILTER_POLICY_TYPE.value == policyType ) { resources = sortBy(serviceDef.rowFilterDef.resources, "itemId"); + } else if ( + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value == policyType + ) { + resources = sortBy(getConstraintResources(serviceDef), "itemId"); } else { resources = sortBy(serviceDef.resources, "itemId"); } return resources; }; +export const getConstraintResources = (serviceDef) => { + let uniqueResources = uniq(flatMap(serviceDef?.constraintDefs, "resources")); + let result = filter(serviceDef?.resources, (item) => + uniqueResources.includes(item.name) + ); + //NOTE : CHANGE ISVALIDLEAF TO FALSE FOR CONSTRAINT POLICY + return map(result, (resource) => ({ + ...resource, + isValidLeaf: false + })); +}; + +export const getConstraintNameFromField = (constraintNameField) => { + if (!constraintNameField) { + return null; + } + if ( + typeof constraintNameField === "object" && + constraintNameField.value != null + ) { + return constraintNameField.value; + } + if (typeof constraintNameField === "string") { + return constraintNameField; + } + return null; +}; + +export const getConstraintDefByName = (serviceDef, constraintName) => { + if (!serviceDef || !constraintName) { + return null; + } + return find(serviceDef.constraintDefs, { name: constraintName }) || null; +}; + +export const getAccessTypesForConstraintDef = (serviceDef, constraintDef) => { + const allAccessTypes = serviceDef?.accessTypes || []; + if (!constraintDef?.accessTypes?.length) { + return allAccessTypes; + } + const allowed = new Set(constraintDef.accessTypes); + return filter(allAccessTypes, (item) => allowed.has(item.name)); +}; + +export const mapAccessTypesToSelectOptions = (accessTypes) => { + return sortBy( + (accessTypes || []).map(({ label, name: value }) => ({ label, value })), + [(option) => option.label.toLowerCase()] + ); +}; + // Get defult landing page export const getLandingPageURl = () => { @@ -1727,7 +1799,11 @@ export const getResourceLevelKeysSorted = (serviceCompDetails) => { return grpResourcesKeys; }; -export const getAccessTypesByResource = (formValues, serviceCompDetails) => { +export const getAccessTypesByResource = ( + formValues, + serviceCompDetails, + getMatchedConstraint +) => { if (!formValues || !serviceCompDetails) { return []; } @@ -1747,41 +1823,127 @@ export const getAccessTypesByResource = (formValues, serviceCompDetails) => { formValues.policyType ) { srcOp = serviceCompDetails.rowFilterDef?.accessTypes || []; + } else if ( + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value == + formValues.policyType + ) { + srcOp = + getAccessTypesFromMatchedDefs(getMatchedConstraint, serviceCompDetails) || + []; } else { srcOp = serviceCompDetails.accessTypes; } - map(formValues.additionalResources, (resourceObj) => { - for (let i = grpResourcesKeys.length - 1; i >= 0; i--) { - let selectedResource = `resourceName-${grpResourcesKeys[i]}`; - if ( - resourceObj[selectedResource] && - resourceObj[selectedResource].value !== accessTypeNoneOption.value - ) { - if (resourceObj[selectedResource].accessTypeRestrictions?.length > 0) { - let op = []; - for (const name of resourceObj[selectedResource] - .accessTypeRestrictions) { - let typeOp = find(srcOp, { name }); - if (typeOp) { - op.push(typeOp); + if ( + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value != + formValues.policyType + ) { + map(formValues.additionalResources, (resourceObj) => { + for (let i = grpResourcesKeys.length - 1; i >= 0; i--) { + let selectedResource = `resourceName-${grpResourcesKeys[i]}`; + if ( + resourceObj[selectedResource] && + resourceObj[selectedResource].value !== accessTypeNoneOption.value + ) { + if ( + resourceObj[selectedResource].accessTypeRestrictions?.length > 0 + ) { + let op = []; + for (const name of resourceObj[selectedResource] + .accessTypeRestrictions) { + let typeOp = find(srcOp, { name }); + if (typeOp) { + op.push(typeOp); + } } + multiplePermissionItem = [...multiplePermissionItem, ...op]; + } else { + multiplePermissionItem = [...srcOp]; } - multiplePermissionItem = [...multiplePermissionItem, ...op]; - } else { - multiplePermissionItem = [...srcOp]; + break; } - break; } - } - }); - srcOp = [...new Set(multiplePermissionItem)]; + }); + srcOp = [...new Set(multiplePermissionItem)]; + } return sortBy( srcOp.map(({ label, name: value }) => ({ label, value })), [(o) => o.label.toLowerCase()] ); }; +// // get constraint policy access types +// export const getConstraintPolicyAccessTypes = ( +// serviceCompDetails, +// formValues +// ) => { +// const additionalResources = formValues?.additionalResources; +// const constraintDefs = serviceCompDetails?.constraintDefs; +// const allAccessTypes = serviceCompDetails?.accessTypes; + +// if ( +// !Array.isArray(additionalResources) || +// !Array.isArray(constraintDefs) || +// !Array.isArray(allAccessTypes) +// ) { +// return []; +// } + +// // Helper: check if two arrays contain the exact same unique resources +// const isExactResourceMatch = (resA = [], resB = []) => { +// if (resA.length !== resB.length) return false; +// const setB = new Set(resB); +// return resA.every((name) => setB.has(name)); +// }; + +// const matchedAccessTypeNames = new Set(); +// const evaluatedSignatures = new Set(); + +// for (const row of additionalResources) { +// // Extract resource names in level order: ["caiiservicecrn", "namespace", ...] +// const resourceNames = []; +// for (const [key, val] of Object.entries(row)) { +// if (key.startsWith("resourceName-") && val?.name) { +// resourceNames.push(val.name); +// } +// } + +// if (resourceNames.length === 0) continue; + +// // Skip redundant evaluations if the same resource combination exists in another row +// const signature = resourceNames.slice().sort().join(","); +// if (evaluatedSignatures.has(signature)) continue; +// evaluatedSignatures.add(signature); + +// // Find all constraintDefs that match this exact resource combination +// for (const def of constraintDefs) { +// if (isExactResourceMatch(resourceNames, def.resources)) { +// if (Array.isArray(def.accessTypes)) { +// def.accessTypes.forEach((type) => matchedAccessTypeNames.add(type)); +// } +// } +// } +// } + +// // Filter full accessType objects that exist in our matched names +// return allAccessTypes.filter((item) => matchedAccessTypeNames.has(item.name)); +// }; + +// export const getAllConstraintAccessTypes = (serviceCompDetails) => { +// const constraintDefs = serviceCompDetails?.constraintDefs || []; +// const accessTypesList = serviceCompDetails?.accessTypes || []; + +// // 1. Collect all unique accessType strings from every constraintDef +// const targetAccessTypeNames = new Set( +// flatMap(constraintDefs, (def) => def.accessTypes || []) +// ); + +// // 2. Filter the full objects matching those names +// return filter(accessTypesList, (item) => +// targetAccessTypeNames.has(item.name) +// ); +// }; + export const prunePolicyItemAccessesToAllowedTypes = ( form, selectedServiceComponentDef @@ -1863,3 +2025,51 @@ export const getPolicyPermissionItemDisplayLbl = ( } } }; + +//Common function to get constraint policy items +export const getMatchedConstraintDefs = (serviceCompDetails, formValues) => { + const additionalResources = formValues?.additionalResources || []; + const constraintDefs = serviceCompDetails?.constraintDefs || []; + + if (isEmpty(additionalResources) || isEmpty(constraintDefs)) { + return []; + } + + // Step 1: Extract resource names from each row (e.g. [['caiiservicecrn', 'namespace', 'endpoint']]) + const allSelectedResources = map(additionalResources, (row) => { + const resourceKeys = pickBy(row, (val, key) => + startsWith(key, "resourceName-") + ); + return map(resourceKeys, "name"); + }); + + // Step 2: Helper to check if two arrays have the same items (regardless of order) + const isMatch = (arr1, arr2) => + arr1.length === arr2.length && isEmpty(difference(arr1, arr2)); + + // Step 3: Find constraintDefs whose resources match any selected row + const matchedDefs = filter(constraintDefs, (def) => + some(allSelectedResources, (selected) => isMatch(selected, def.resources)) + ); + + // Step 4: Remove duplicates by def.name + return uniqBy(matchedDefs, "name"); +}; + +export const getAccessTypesFromMatchedDefs = ( + matchedDefs, + serviceCompDetails +) => { + const allAccessTypes = serviceCompDetails?.accessTypes || []; + + // Extract unique accessType names: ['execute', 'select', ...] + const accessTypeNames = _.chain(matchedDefs) + .flatMap("accessTypes") + .uniq() + .value(); + + // Filter full objects from serviceCompDetails.accessTypes + return _.filter(allAccessTypes, (item) => + _.includes(accessTypeNames, item.name) + ); +}; diff --git a/security-admin/src/main/webapp/react-webapp/src/utils/appState.js b/security-admin/src/main/webapp/react-webapp/src/utils/appState.js index 7de43d08e..a15a7096d 100644 --- a/security-admin/src/main/webapp/react-webapp/src/utils/appState.js +++ b/security-admin/src/main/webapp/react-webapp/src/utils/appState.js @@ -32,16 +32,62 @@ function getServiceDef() { return serviceDefData; } +function updateServiceDef(serviceDef, constraintDefs) { + return serviceDef.map((item) => { + if (item.id === 208) { + return { ...item, constraintDefs: constraintDefs }; // or shorthand { ...item, constraintDefs } + } + return item; + }); +} + function setServiceDef( serviceDef, tagServiceDef, gdsServiceDef, allServiceDefs ) { - serviceDefData.serviceDefs = serviceDef; + let constraintDefs = [ + { + itemId: 1, + name: "requireApproval", + label: "Require approval", + resources: ["caiiservicecrn", "namespace", "endpoint"], + accessTypes: ["view", "access"], + parameters: [ + { + itemId: 1, + name: "approver", + label: "Approver", + type: "user", + mandatory: true + } + ] + }, + { + itemId: 2, + name: "maxRows", + label: "Maximum rows", + resources: ["caiiservicecrn", "namespace", "application"], + accessTypes: ["access", "manage"], + parameters: [ + { + itemId: 1, + name: "rowCount", + type: "int", + mandatory: true + } + ] + } + ]; + + serviceDefData.serviceDefs = updateServiceDef(serviceDef, constraintDefs); serviceDefData.tagServiceDefs = tagServiceDef; serviceDefData.gdsServiceDef = gdsServiceDef; - serviceDefData.allServiceDefs = allServiceDefs; + serviceDefData.allServiceDefs = updateServiceDef( + allServiceDefs, + constraintDefs + ); } export { getUserProfile, setUserProfile, getServiceDef, setServiceDef }; 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 46273abd7..97ff12b19 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 @@ -669,7 +669,8 @@ const CreateNewPolicyForm = () => { isDenyAllElse: false, // For deny all toggle isEnabled: true, // Policy enabled by default isAuditEnabled: true, // Audit enabled by default - policyPriority: 0 // Normal priority by default + policyPriority: 0, // Normal priority by default + constraintPolicyItems: [{}] // For constraint }} blockUI={blockUI} preventUnBlock={preventUnBlock} diff --git a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx index 448077256..4038fd0f7 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/AccessRulesStep.jsx @@ -132,6 +132,12 @@ const AccessRulesStep = ({ title: "Row Filter Rules", msg: "Row filter rules grants access to resources when the specific criteria are met." }); + } else if (policyType == 4) { + policyPermissionItems.push({ + attrName: "constraintPolicyItems", + title: "Constraint Rules", + msg: "Constraint rules grants access to resources when the specific criteria are met." + }); } else if (policyType == 0 && !enableDenyAndExceptions) { policyPermissionItems.push({ attrName: "policyItems", diff --git a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx index 0ac3fd996..d153cfe5a 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/PolicyPermissionItemVertical.jsx @@ -18,6 +18,7 @@ */ import React, { useState } from "react"; +import Select from "react-select"; import { FieldArray } from "react-final-form-arrays"; import { Row, @@ -35,7 +36,12 @@ import UsersGroupsAndRolesSelectionComponent from "./UsersGroupsAndRolesSelectio import TagBasePermissionItem from "../../TagBasePermissionItem"; import { getAccessTypesByResource, - getPolicyConditionDisplayLbl + getPolicyConditionDisplayLbl, + getMatchedConstraintDefs, + getConstraintNameFromField, + getConstraintDefByName, + getAccessTypesForConstraintDef, + mapAccessTypesToSelectOptions } from "Utils/XAUtils"; import { CustomTooltip } from "Components/CommonComponents"; import { getSelectedAccessTypesForRow } from "Utils/policyConditionUtils"; @@ -52,6 +58,7 @@ export default function PolicyPermissionItemVertical({ const [showTagPermissionModalIndex, setShowTagPermissionModalIndex] = useState(null); const form = useForm(); // Get form instance + let getMatchedConstraint = []; const { conditionDefVal, actionReqsMap, leafResourceTypes } = usePolicyPermissionConditionContext({ @@ -61,12 +68,26 @@ export default function PolicyPermissionItemVertical({ form, enableResourcePruneDefer }); - + if ( + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value == + formValues.policyType + ) { + getMatchedConstraint = getMatchedConstraintDefs( + serviceCompDetails, + formValues + ); + } const accessTypeOptions = getAccessTypesByResource( formValues, - serviceCompDetails + serviceCompDetails, + getMatchedConstraint ); + const constraintSelectOptions = getMatchedConstraint.map((constraint) => ({ + label: constraint.label || constraint.name, + value: constraint.name + })); + const getMaskingAccessTypeOptions = (index) => { if (serviceCompDetails?.dataMaskDef?.maskTypes?.length > 0) { if ( @@ -121,6 +142,14 @@ export default function PolicyPermissionItemVertical({ // Required field validation const required = (value) => (value ? undefined : "Required"); + const constraintSelectFirstMsg = "Please select constraint name first"; + + const handleConstraintNameChange = (input, fieldName, option) => { + input.onChange(option || null); + form.change(`${fieldName}.accesses`, undefined); + form.change(`${fieldName}.constraintInfo.parameters`, undefined); + }; + // Helper function to clear all data from a permission item const clearPermissionItem = (index) => { // Reset the entire permission item to an empty object @@ -171,11 +200,81 @@ export default function PolicyPermissionItemVertical({ </Row> )} - {fields.map((name, index) => ( + {fields.map((name, index) => { + const isConstraintPolicy = + formValues?.policyType === + RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value; + const rowConstraintName = getConstraintNameFromField( + fields.value[index]?.constraintInfo?.constraintName + ); + const rowConstraintDef = getConstraintDefByName( + serviceCompDetails, + rowConstraintName + ); + const rowAccessTypeOptions = rowConstraintDef + ? mapAccessTypesToSelectOptions( + getAccessTypesForConstraintDef( + serviceCompDetails, + rowConstraintDef + ) + ) + : []; + const permissionAccessOptions = isConstraintPolicy + ? rowAccessTypeOptions + : accessTypeOptions; + return ( <React.Fragment key={name}> <Row key={name} className="py-3"> {/* Rule Header */} <Col sm={11}> + {RangerPolicyType.RANGER_CONSTRAINT_POLICY_TYPE.value == + formValues?.policyType && ( + <Col xs={12} className="mb-4"> + <Row> + <Col className="column-1-fixed text-end"> + <FormB.Label>Constraint Name</FormB.Label> + </Col> + <> + <Col> + <Field + name={`${name}.constraintInfo.constraintName`} + validate={required} + > + {({ input, meta }) => ( + <> + <Select + inputId={`${name}-constraint-name`} + options={constraintSelectOptions} + value={input.value} + onChange={(option) => + handleConstraintNameChange( + input, + name, + option + ) + } + onBlur={input.onBlur} + placeholder="Select constraint" + tabSelectsValue={false} + isClearable + isDisabled={ + constraintSelectOptions.length === 0 + } + /> + {meta.touched && meta.error && ( + <span className="text-danger d-block mt-1"> + {meta.error} + </span> + )} + </> + )} + </Field> + </Col> + <Col className="column-3-fixed"></Col> + </> + </Row> + </Col> + )} {/* Permissions Section */} <Col xs={12} className="mb-4"> <Row> @@ -183,8 +282,12 @@ export default function PolicyPermissionItemVertical({ <FormB.Label>Permissions</FormB.Label> </Col> <Col> - {/* Tag-based policy - use modal with table layout */} - {serviceCompDetails?.name === "tag" ? ( + {isConstraintPolicy && !rowConstraintName ? ( + <p className="text-muted mb-0"> + {constraintSelectFirstMsg} + </p> + ) : serviceCompDetails?.name === "tag" ? ( + /* Tag-based policy - use modal with table layout */ <Field name={`${name}.accesses`}> {({ input, meta }) => ( <> @@ -265,7 +368,7 @@ export default function PolicyPermissionItemVertical({ {/* Tag Permission Modal */} {showTagPermissionModalIndex === index && ( <TagBasePermissionItem - options={accessTypeOptions} + options={permissionAccessOptions} inputVal={{ ...input, onChange: (value) => { @@ -298,8 +401,8 @@ export default function PolicyPermissionItemVertical({ {({ input, meta }) => { const currentValue = input.value || []; const allSelected = - accessTypeOptions.length > 0 && - accessTypeOptions.every((access) => + permissionAccessOptions.length > 0 && + permissionAccessOptions.every((access) => currentValue.some( (a) => a.type === access.value ) @@ -311,13 +414,12 @@ export default function PolicyPermissionItemVertical({ input.onChange([]); } else { // Select all - const allAccesses = accessTypeOptions.map( - (a) => ({ + const allAccesses = + permissionAccessOptions.map((a) => ({ type: a.value, isAllowed: true, label: a.label - }) - ); + })); input.onChange(allAccesses); } }; @@ -327,7 +429,7 @@ export default function PolicyPermissionItemVertical({ <Container fluid className="p-0"> <div className="gx-3 gy-1 permissions-checkboxes"> {/* Select All Link - Only show if more than 1 option */} - {accessTypeOptions.length > 1 && ( + {permissionAccessOptions.length > 1 && ( <div className="mb-1"> <FormB.Check type="checkbox" @@ -339,7 +441,8 @@ export default function PolicyPermissionItemVertical({ /> </div> )} - {accessTypeOptions.map((access) => { + {permissionAccessOptions.map( + (access) => { const isChecked = Array.isArray(input.value) && input.value.some( @@ -382,7 +485,8 @@ export default function PolicyPermissionItemVertical({ /> </div> ); - })} + } + )} </div> </Container> {meta.error && ( @@ -569,6 +673,78 @@ export default function PolicyPermissionItemVertical({ </Col> )} + {isConstraintPolicy && ( + <Col xs={12} className="mb-4"> + <Row> + <Col className="column-1-fixed text-end"> + <FormB.Label>Parameters</FormB.Label> + </Col> + <Col> + {!rowConstraintName ? ( + <p className="text-muted mb-0"> + {constraintSelectFirstMsg} + </p> + ) : ( + (rowConstraintDef?.parameters || []).map( + (param) => ( + <Row key={param.name} className="mb-3"> + <Col sm={4} md={3} className="text-end"> + <FormB.Label className="mb-0"> + {param.label || param.name} + {param.mandatory ? ( + <span className="required-mark"> + * + </span> + ) : null} + </FormB.Label> + </Col> + <Col sm={8} md={6}> + <Field + name={`${name}.constraintInfo.parameters.${param.name}`} + validate={ + param.mandatory ? required : undefined + } + > + {({ input, meta }) => ( + <> + <FormB.Control + {...input} + value={input.value ?? ""} + type={ + param.type === "int" + ? "number" + : "text" + } + placeholder={ + param.type === "user" || + param.subType === "user" || + param.type === "group" || + param.type === "role" + ? "Enter user, group, or role" + : `Enter ${param.label || param.name}` + } + isInvalid={ + meta.touched && meta.error + } + /> + {meta.touched && meta.error && ( + <FormB.Control.Feedback type="invalid"> + {meta.error} + </FormB.Control.Feedback> + )} + </> + )} + </Field> + </Col> + </Row> + ) + ) + )} + </Col> + </Row> + </Col> + )} + {/* Data Masking Option (for Masking policies) */} {RangerPolicyType.RANGER_MASKING_POLICY_TYPE.value == formValues?.policyType && ( @@ -886,7 +1062,8 @@ export default function PolicyPermissionItemVertical({ </Row> <hr /> </React.Fragment> - ))} + ); + })} {/* Add More Rules Button */} <Row className="my-3"> diff --git a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ServiceStep.jsx b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ServiceStep.jsx index 215545f88..8cbfd7e3c 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ServiceStep.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/PolicyListing/NewPolicyForm/PolicyCreateStepperStepComp/ServiceStep.jsx @@ -30,7 +30,8 @@ import { isRenderRowFilter, getSelectBoxErrorStyles, isKeyAdmin, - isKMSAuditor + isKMSAuditor, + isRenderConstraint } from "Utils/XAUtils"; const required = (value) => (value ? undefined : "Required"); @@ -287,8 +288,9 @@ const ServiceStep = ({ {/* Policy Type */} {selectedServiceComponentDef && (isRenderMasking(selectedServiceComponentDef.dataMaskDef) || - isRenderRowFilter( - selectedServiceComponentDef.rowFilterDef + isRenderRowFilter(selectedServiceComponentDef.rowFilterDef) || + isRenderConstraint( + selectedServiceComponentDef?.constraintDefs )) && ( <FormB.Group as={Row} className="mb-4"> <FormB.Label column className="column-1-fixed text-end"> @@ -415,6 +417,46 @@ const ServiceStep = ({ </small> </div> )} + + {/* Constraint Policy - Value: 4 */} + {isRenderConstraint( + selectedServiceComponentDef?.constraintDefs + ) && ( + <div + className="card p-3 w-33" + style={{ + cursor: "pointer", + border: + input.value === 4 + ? "2px solid #0b7fad" + : "1px solid #dee2e6" + }} + onClick={() => handlePolicyTypeChange(input, 4)} + > + <div className="form-check"> + <input + className="form-check-input" + type="radio" + name="policyType" + id="constraint" + value={4} + checked={input.value === 4} + onChange={() => + handlePolicyTypeChange(input, 4) + } + /> + <label + className="form-check-label" + htmlFor="constraint" + > + <strong>Constraint</strong> + </label> + </div> + <small className="text-muted mt-2"> + Applies constraints to the resource + </small> + </div> + )} </div> )} </Field> diff --git a/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx b/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx index 119911325..8681bff1f 100644 --- a/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx +++ b/security-admin/src/main/webapp/react-webapp/src/views/Resources/ResourceComp.jsx @@ -48,7 +48,7 @@ export default function ResourceComp(props) { const [rsrcState, setLoader] = useState({ loader: false, resourceKey: -1 }); const toastId = useRef(null); - let resources = getResourcesDefVal(serviceCompDetails, policyType); + const resources = getResourcesDefVal(serviceCompDetails, policyType); useEffect(() => { if (rsrcState.loader) {
