diff --git a/src/components/MemberOf/MemberOfHbacRules.tsx b/src/components/MemberOf/MemberOfHbacRules.tsx index 82a37ad4..1395762b 100644 --- a/src/components/MemberOf/MemberOfHbacRules.tsx +++ b/src/components/MemberOf/MemberOfHbacRules.tsx @@ -6,12 +6,19 @@ import { User, HBACRule } from "src/utils/datatypes/globalDataTypes"; // Components import MemberOfToolbar, { MembershipDirection } from "./MemberOfToolbar"; import MemberOfHbacRulesTable from "./MemberOfTableHbacRules"; +import MemberOfAddModal, { AvailableItems } from "./MemberOfAddModal"; // Hooks import useAlerts from "src/hooks/useAlerts"; // RPC -import { useGetHbacRulesInfoByNameQuery } from "src/services/rpc"; +import { + ErrorResult, + useAddToHbacRulesMutation, + useGetHbacRulesInfoByNameQuery, + useGettingHbacRulesQuery, +} from "src/services/rpc"; // Utils import { API_VERSION_BACKUP, paginate } from "src/utils/utils"; +import { apiToHBACRule } from "src/utils/hbacRulesUtils"; interface MemberOfHbacRulesProps { user: Partial; @@ -103,10 +110,103 @@ const MemberOfHbacRules = (props: MemberOfHbacRulesProps) => { const someItemSelected = hbacRulesSelected.length > 0; const showTableRows = hbacRules.length > 0; + // Dialogs and actions + const [showAddModal, setShowAddModal] = React.useState(false); + // Buttons functionality // - Refresh const isRefreshButtonEnabled = !fullHbacRulesQuery.isFetching && !props.isUserDataLoading; + const isAddButtonEnabled = + membershipDirection !== "indirect" && isRefreshButtonEnabled; + + // Add new member to 'HBAC rules' + // API calls + const [addMemberToHbacRules] = useAddToHbacRulesMutation(); + const [adderSearchValue, setAdderSearchValue] = React.useState(""); + const [availableHbacRules, setAvailableHbacRules] = React.useState< + HBACRule[] + >([]); + const [availableItems, setAvailableItems] = React.useState( + [] + ); + + // Load available HBAC rules, delay the search for opening the modal + const hbacRulesQuery = useGettingHbacRulesQuery({ + search: adderSearchValue, + apiVersion: API_VERSION_BACKUP, + sizelimit: 100, + startIdx: 0, + stopIdx: 100, + }); + + // Trigger available HBAC rules search + React.useEffect(() => { + if (showAddModal) { + hbacRulesQuery.refetch(); + } + }, [showAddModal, adderSearchValue, props.user]); + + // Update available HBAC rules + React.useEffect(() => { + if (hbacRulesQuery.data && !hbacRulesQuery.isFetching) { + // transform data to HBAC rules + const count = hbacRulesQuery.data.result.count; + const results = hbacRulesQuery.data.result.results; + let items: AvailableItems[] = []; + const avalHbacRules: HBACRule[] = []; + for (let i = 0; i < count; i++) { + const hbacRule = apiToHBACRule(results[i].result); + avalHbacRules.push(hbacRule); + items.push({ + key: hbacRule.cn, + title: hbacRule.cn, + }); + } + items = items.filter((item) => !hbacRulesNamesToLoad.includes(item.key)); + + setAvailableHbacRules(avalHbacRules); + setAvailableItems(items); + } + }, [hbacRulesQuery.data, hbacRulesQuery.isFetching]); + + // - Add + const onAddHbacRule = (items: AvailableItems[]) => { + const uid = props.user.uid; + const newHbacRuleNames = items.map((item) => item.key); + if (uid === undefined || newHbacRuleNames.length == 0) { + return; + } + + addMemberToHbacRules([uid, "user", newHbacRuleNames]).then((response) => { + if ("data" in response) { + if (response.data.result) { + // Set alert: success + alerts.addAlert( + "add-member-success", + `Assigned new HBAC rule to user ${uid}`, + "success" + ); + // Update displayed HBAC Rules before they are updated via refresh + const newHbacRules = hbacRules.concat( + availableHbacRules.filter((hbacRule) => + newHbacRuleNames.includes(hbacRule.cn) + ) + ); + setHbacRules(newHbacRules); + + // Refresh data + props.onRefreshUserData(); + // Close modal + setShowAddModal(false); + } else if (response.data.error) { + // Set alert: error + const errorMessage = response.data.error as unknown as ErrorResult; + alerts.addAlert("add-member-error", errorMessage.message, "danger"); + } + } + }); + }; return ( <> @@ -121,9 +221,8 @@ const MemberOfHbacRules = (props: MemberOfHbacRulesProps) => { deleteButtonEnabled={someItemSelected} // eslint-disable-next-line @typescript-eslint/no-empty-function onDeleteButtonClick={() => {}} - addButtonEnabled={true} - // eslint-disable-next-line @typescript-eslint/no-empty-function - onAddButtonClick={() => {}} + addButtonEnabled={isAddButtonEnabled} + onAddButtonClick={() => setShowAddModal(true)} membershipDirectionEnabled={true} membershipDirection={membershipDirection} onMembershipDirectionChange={setMembershipDirection} @@ -150,6 +249,17 @@ const MemberOfHbacRules = (props: MemberOfHbacRulesProps) => { onSetPage={(_e, page) => setPage(page)} onPerPageSelect={(_e, perPage) => setPerPage(perPage)} /> + {showAddModal && ( + setShowAddModal(false)} + availableItems={availableItems} + onAdd={onAddHbacRule} + onSearchTextChange={setAdderSearchValue} + title={`Assign HBAC rule to user ${props.user.uid}`} + ariaLabel="Add user of HBAC rule modal" + /> + )} ); }; diff --git a/src/services/rpc.ts b/src/services/rpc.ts index fe32a36d..30f99ad3 100644 --- a/src/services/rpc.ts +++ b/src/services/rpc.ts @@ -170,7 +170,7 @@ export interface GenericPayload { | "group" | "netgroups" | "role" - | "hbacRule"; + | "hbacrule"; } export interface GroupShowPayload { @@ -793,7 +793,7 @@ export const api = createApi({ } } - if (objName === "hbacRule") { + if (objName === "hbacrule") { if (description) { params["description"] = description; } @@ -836,7 +836,7 @@ export const api = createApi({ id = idResponseData.result.result[i] as cnType; } else if (objName === "role") { id = idResponseData.result.result[i] as roleType; - } else if (objName === "hbacRule") { + } else if (objName === "hbacrule") { id = idResponseData.result.result[i] as cnType; } else { // Unknown, should never happen @@ -1645,6 +1645,44 @@ export const api = createApi({ return hbacRulesList; }, }), + /** + * Add entity to HBAC rules + * @param {string} toId - ID of the entity to add to HBAC rules + * @param {string} type - Type of the entity + * Available types: user | host | service | sourcehost + * @param {string[]} listOfMembers - List of members to add to the HBAC rules + */ + addToHbacRules: build.mutation< + BatchRPCResponse, + [string, string, string[]] + >({ + query: (payload) => { + const memberId = payload[0]; + const memberType = payload[1]; + const roleNames = payload[2]; + + let methodType = ""; + if (memberType === "user") { + methodType = "hbacrule_add_user"; + } else if (memberType === "host") { + methodType = "hbacrule_add_host"; + } else if (memberType === "service") { + methodType = "hbacrule_add_service"; + } else if (memberType === "sourcehost") { + methodType = "hbacrule_add_sourcehost"; + } + + const membersToAdd: Command[] = []; + roleNames.map((roleName) => { + const payloadItem = { + method: methodType, + params: [[roleName], { [memberType]: memberId }], + } as Command; + membersToAdd.push(payloadItem); + }); + return getBatchCommand(membersToAdd, API_VERSION_BACKUP); + }, + }), }), }); @@ -1708,8 +1746,9 @@ export const useGettingRolesQuery = (payloadData, options) => { payloadData["objAttr"] = "cn"; return useGettingGenericQuery(payloadData, options); }; +// HBAC rules export const useGettingHbacRulesQuery = (payloadData) => { - payloadData["objName"] = "hbacRule"; + payloadData["objName"] = "hbacrule"; payloadData["objAttr"] = "cn"; return useGettingGenericQuery(payloadData); }; @@ -1796,4 +1835,5 @@ export const { useRemoveFromRolesMutation, useGetRolesInfoByNameQuery, useGetHbacRulesInfoByNameQuery, + useAddToHbacRulesMutation, } = api;