diff --git a/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/RoomInfoABACSection.spec.tsx b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/RoomInfoABACSection.spec.tsx new file mode 100644 index 0000000000000..7d3f745f655c7 --- /dev/null +++ b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/RoomInfoABACSection.spec.tsx @@ -0,0 +1,79 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { mockAppRoot } from '@rocket.chat/mock-providers'; +import { render, screen } from '@testing-library/react'; +import { axe } from 'jest-axe'; + +import RoomInfoABACSection from './RoomInfoABACSection'; +import { createFakeRoom } from '../../../../../../../tests/mocks/data'; + +type RoomWithABAC = IRoom & { + abacAttributes?: { + key: string; + values: string[]; + }[]; +}; + +describe('RoomInfoABACSection', () => { + const createRoomWithABAC = (attributes: { key: string; values: string[] }[]): RoomWithABAC => { + const room = createFakeRoom(); + return { + ...room, + abacAttributes: attributes, + } as RoomWithABAC; + }; + + const appRootWithABACEnabled = mockAppRoot().withSetting('ABAC_Enabled', true).withSetting('ABAC_ShowAttributesInRooms', true).build(); + + describe('Conditional rendering', () => { + it('should return null when ABAC_Enabled is false', () => { + const room = createRoomWithABAC([{ key: 'Test', values: ['Value1'] }]); + const appRoot = mockAppRoot().withSetting('ABAC_Enabled', false).withSetting('ABAC_ShowAttributesInRooms', true).build(); + + render(, { wrapper: appRoot }); + expect(screen.queryByText('ABAC_Managed')).not.toBeInTheDocument(); + }); + + it('should return null when ABAC_ShowAttributesInRooms is false', () => { + const room = createRoomWithABAC([{ key: 'Test', values: ['Value1'] }]); + const appRoot = mockAppRoot().withSetting('ABAC_Enabled', true).withSetting('ABAC_ShowAttributesInRooms', false).build(); + + render(, { wrapper: appRoot }); + expect(screen.queryByText('ABAC_Managed')).not.toBeInTheDocument(); + }); + + it('should return null when abacAttributes is empty', () => { + const room = createRoomWithABAC([]); + render(, { wrapper: appRootWithABACEnabled }); + expect(screen.queryByText('ABAC_Managed')).not.toBeInTheDocument(); + }); + + it('should render when all conditions are met', () => { + const room = createRoomWithABAC([{ key: 'Test', values: ['Value1'] }]); + render(, { wrapper: appRootWithABACEnabled }); + expect(screen.getByText('ABAC_Managed')).toBeInTheDocument(); + }); + }); + + describe('Accessibility', () => { + it('should have no accessibility violations', async () => { + const room = createRoomWithABAC([ + { key: 'Chat-sensitivity', values: ['Classified', 'Top-Secret'] }, + { key: 'Country', values: ['US-only'] }, + ]); + const { container } = render(, { wrapper: appRootWithABACEnabled }); + + const results = await axe(container); + expect(results).toHaveNoViolations(); + }); + }); + describe('Snapshot', () => { + it('should match the snapshot', () => { + const room = createRoomWithABAC([ + { key: 'Chat-sensitivity', values: ['Classified', 'Top-Secret'] }, + { key: 'Country', values: ['US-only'] }, + ]); + const { baseElement } = render(, { wrapper: appRootWithABACEnabled }); + expect(baseElement).toMatchSnapshot(); + }); + }); +}); diff --git a/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/RoomInfoABACSection.tsx b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/RoomInfoABACSection.tsx new file mode 100644 index 0000000000000..8c11bdde68f06 --- /dev/null +++ b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/RoomInfoABACSection.tsx @@ -0,0 +1,66 @@ +import type { IRoom } from '@rocket.chat/core-typings'; +import { Box, Divider, Tag } from '@rocket.chat/fuselage'; +import { useSetting } from '@rocket.chat/ui-contexts'; +import { useTranslation } from 'react-i18next'; + +import { InfoPanelField, InfoPanelLabel } from '../../../../../../components/InfoPanel'; +import { RoomIcon } from '../../../../../../components/RoomIcon'; + +// TODO: Remove type union when ABAC is implemented +type RoomInfoABACSectionProps = { + room: IRoom & { + abacAttributes?: { + key: string; + values: string[]; + }[]; + }; +}; + +const RoomInfoABACSection = ({ room }: RoomInfoABACSectionProps) => { + const { t } = useTranslation(); + + const abacEnabled = useSetting('ABAC_Enabled'); + const showAttributesInRoom = useSetting('ABAC_ShowAttributesInRooms'); + + if (!abacEnabled || !showAttributesInRoom || !room.abacAttributes?.length) { + return null; + } + + return ( + <> + + + + + + + {t('ABAC_Managed')} + + + + + {t('ABAC_Managed_description')} + + {t('ABAC_Room_Attributes')} + + {room.abacAttributes.map((attribute) => ( + + + {attribute.key} + + + {attribute.values.map((value) => ( + + {value} + + ))} + + + ))} + + + + ); +}; + +export default RoomInfoABACSection; diff --git a/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/__snapshots__/RoomInfoABACSection.spec.tsx.snap b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/__snapshots__/RoomInfoABACSection.spec.tsx.snap new file mode 100644 index 0000000000000..43ec592f3181f --- /dev/null +++ b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/ABAC/__snapshots__/RoomInfoABACSection.spec.tsx.snap @@ -0,0 +1,123 @@ +// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing + +exports[`RoomInfoABACSection Snapshot should match the snapshot 1`] = ` + +
+
+
+
+ + +
+ +
+ ABAC_Managed +
+
+
+
+
+ ABAC_Managed_description +
+ ABAC_Room_Attributes +
+
    +
  • + + Chat-sensitivity + +
      +
    • + + + Classified + + +
    • +
    • + + + Top-Secret + + +
    • +
    +
  • +
  • + + Country + +
      +
    • + + + US-only + + +
    • +
    +
  • +
+
+
+ +`; diff --git a/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.stories.tsx b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.stories.tsx index 3feb078e0c68e..03a8e0a71447f 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.stories.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.stories.tsx @@ -1,7 +1,9 @@ import type { RoomType } from '@rocket.chat/core-typings'; +import { mockAppRoot } from '@rocket.chat/mock-providers'; import type { Meta, StoryFn } from '@storybook/react'; import RoomInfo from './RoomInfo'; +import FakeRoomProvider from '../../../../../../tests/mocks/client/FakeRoomProvider'; import { Contextualbar } from '../../../../../components/Contextualbar'; export default { @@ -10,7 +12,13 @@ export default { layout: 'fullscreen', actions: { argTypesRegex: '^on[A-Z].*' }, }, - decorators: [(fn) => {fn()}], + decorators: [ + (fn) => ( + + {fn()} + + ), + ], args: { icon: 'lock', }, @@ -61,3 +69,25 @@ Broadcast.args = { broadcast: true, }, }; + +export const ABAC = Template.bind({}); +ABAC.decorators = [ + mockAppRoot().withSetting('ABAC_Enabled', true).withSetting('ABAC_ShowAttributesInRooms', true).buildStoryDecorator(), + (fn) => ( + + {fn()} + + ), +]; +ABAC.args = { + ...Default.args, + room: { + ...roomArgs, + // @ts-expect-error - abacAttributes is not yet implemented in Rooms properties + abacAttributes: [ + { name: 'Chat-sensitivity', values: ['Classified', 'Top-Secret'] }, + { name: 'Country', values: ['US-only'] }, + { name: 'Project', values: ['Ruminator-2000'] }, + ], + }, +}; diff --git a/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.tsx b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.tsx index ffb0ef73ebdb5..23b2c1eaf2b66 100644 --- a/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.tsx +++ b/apps/meteor/client/views/room/contextualBar/Info/RoomInfo/RoomInfo.tsx @@ -29,6 +29,7 @@ import MarkdownText from '../../../../../components/MarkdownText'; import { useRetentionPolicy } from '../../../hooks/useRetentionPolicy'; import { useRoomActions } from '../hooks/useRoomActions'; import { useSplitRoomActions } from '../hooks/useSplitRoomActions'; +import RoomInfoABACSection from './ABAC/RoomInfoABACSection'; type RoomInfoProps = { room: IRoom; @@ -128,6 +129,7 @@ const RoomInfo = ({ room, icon, onClickBack, onClickClose, onClickEnterRoom, onC )} {retentionPolicy?.isActive && } + diff --git a/packages/i18n/src/locales/en.i18n.json b/packages/i18n/src/locales/en.i18n.json index 7991ab6c17650..640ef7f49df6f 100644 --- a/packages/i18n/src/locales/en.i18n.json +++ b/packages/i18n/src/locales/en.i18n.json @@ -77,6 +77,9 @@ "A_new_owner_will_be_assigned_automatically_to_those__count__rooms__rooms__": "A new owner will be assigned automatically to those {{count}} rooms:
{{rooms}}.", "A_secure_and_highly_private_self-managed_solution_for_conference_calls": "A secure and highly private self-managed solution for conference calls.", "A_workspace_admin_needs_to_install_and_configure_a_conference_call_app": "A workspace admin needs to install and configure a conference call app.", + "ABAC_Managed": "ABAC Managed", + "ABAC_Managed_description": "Only compliant users have access to attribute-based access controlled rooms. Attributes determine room access.", + "ABAC_Room_Attributes": "Room Attributes", "Accept": "Accept", "Accept_Call": "Accept Call", "Accept_incoming_livechat_requests_even_if_there_are_no_online_agents": "Accept incoming omnichannel requests even if there are no online agents",