Skip to content

Commit

Permalink
ISPN-14702 Grant and deny new access
Browse files Browse the repository at this point in the history
  • Loading branch information
karesti committed Nov 16, 2023
1 parent d3926af commit f0204d4
Show file tree
Hide file tree
Showing 19 changed files with 761 additions and 71 deletions.
22 changes: 22 additions & 0 deletions cypress/e2e/1_acess_management.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,26 @@ describe('Global stats', () => {
cy.contains('aRole description').should('not.exist');
});

it('successfully creates, updates and removes a principal', () => {
cy.login(Cypress.env('username'), Cypress.env('password'), '/access-management');
cy.get('[aria-label=nav-item-principals').click();
cy.contains('No principals').click();
cy.get('[data-cy=grantAccessPrincipalButton').click();
cy.get("[aria-label=principal-name-input]").type("aPrincipal");
cy.get("[data-cy=menu-toogle-roles]").click();
cy.get("[data-cy=option-typeahead-deployer]").click();
cy.get("[data-cy=menu-toogle-roles]").click();
cy.get('[aria-label=Save').click();
// cy.contains('Access granted to aPrincipal.');
// cy.get("[aria-label=aPrincipal-menu]").click();
// cy.get("[aria-label=manageRoles]").click();
// cy.get("[data-cy=menu-toogle-roles]").click();
// cy.get("[data-cy=option-typeahead-admin]").click();
// cy.get("[data-cy=option-typeahead-deployer]").click();
// cy.get("[data-cy=menu-toogle-roles]").click();
// cy.get('[aria-label=Save').click();
// cy.contains('Roles [admin] granted to aPrincipal.');
// TODO: remove needs to be tested with REST API fixes
});

});
2 changes: 1 addition & 1 deletion src/__tests__/views/NotFound.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const mockNavigate = jest.fn();

jest.mock('react-router', () => ({
...jest.requireActual('react-router'),
useNavigate: () => mockNavigate,
useNavigate: () => mockNavigate
}));

describe('Not found page', () => {
Expand Down
17 changes: 10 additions & 7 deletions src/app/AccessManagement/AccessManager.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ import { useTranslation } from 'react-i18next';
import { RoleTableDisplay } from '@app/AccessManagement/RoleTableDisplay';
import { FlushRoleCacheModal } from '@app/AccessManagement/FlushRoleCacheModal';
import { PrincipalTableDisplay } from '@app/AccessManagement/PrincipalTableDisplay';
import { IAction } from '@patternfly/react-table';

const AccessManager = () => {
const { t } = useTranslation();
const brandname = t('brandname.brandname');
const [activeTabKey, setActiveTabKey] = useState('0');
const [activeTabKey, setActiveTabKey] = useState<'roles' | 'principals'>('roles');
const [showRoles, setShowRoles] = useState(true);
const [showAccessControl, setShowAccessControl] = useState(false);
const [isOpen, setIsOpen] = useState(false);
Expand All @@ -51,21 +52,23 @@ const AccessManager = () => {
const handleTabClick = (ev, nav) => {
const tabIndex = nav.itemId;
setActiveTabKey(tabIndex);
setShowRoles(tabIndex == '0');
setShowAccessControl(tabIndex == '1');
setShowRoles(tabIndex == 'roles');
setShowAccessControl(tabIndex == 'principals');
};

const buildTabs = () => {
const tabs: AccessTab[] = [
{ name: t('access-management.tab-roles'), key: '0' },
{ name: t('access-management.tab-access-control'), key: '1' },
{ name: t('access-management.tab-roles'), key: 'roles' },
{ name: t('access-management.tab-access-control'), key: 'principals' }
];

return (
<Nav data-cy="navigationTabs" onSelect={handleTabClick} variant={'tertiary'}>
<Nav onSelect={handleTabClick} variant={'tertiary'}>
<NavList>
{tabs.map((tab) => (
<NavItem
aria-label={'nav-item-' + tab.name}
data-cy={'nav-item-' + tab.key}
aria-label={'nav-item-' + tab.key}
key={'nav-item-' + tab.key}
itemId={tab.key}
isActive={activeTabKey === tab.key}
Expand Down
4 changes: 2 additions & 2 deletions src/app/AccessManagement/CreateRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ import { useTranslation } from 'react-i18next';
import formUtils, { IField } from '@services/formUtils';
import { AddCircleOIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
import { useCreateRole, useFetchAvailableRoles } from '@app/services/rolesHook';
import { PERMISSIONS_MAP } from '@services/infinispanRefData';
import { ROLES_MAP } from '@services/infinispanRefData';
import { SelectMultiWithChips } from '@app/Common/SelectMultiWithChips';

const CreateRole = (props: { isModalOpen: boolean; submitModal: () => void; closeModal: () => void }) => {
const { t } = useTranslation();
const { roles } = useFetchAvailableRoles();
const initPermissions = () => {
const array: SelectOptionProps[] = [];
PERMISSIONS_MAP.forEach((value, key, map) => {
ROLES_MAP.forEach((value, key, map) => {
const desc = t(value);
array.push({
id: key,
Expand Down
193 changes: 193 additions & 0 deletions src/app/AccessManagement/GrantNewAccess.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import React, { useEffect, useState } from 'react';
import {
Button,
ButtonVariant,
Form,
FormGroup,
FormHelperText,
HelperText,
HelperTextItem,
Modal,
ModalVariant,
SelectOptionProps,
TextInput
} from '@patternfly/react-core';
import { useTranslation } from 'react-i18next';
import formUtils, { IField } from '@services/formUtils';
import { AddCircleOIcon, ExclamationCircleIcon } from '@patternfly/react-icons';
import { useFetchAvailableRoles } from '@app/services/rolesHook';
import { SelectMultiWithChips } from '@app/Common/SelectMultiWithChips';
import { useFetchAvailablePrincipals, useGrantAccess } from '@app/services/principalsHook';

const GrantNewAccess = (props: { isModalOpen: boolean; submitModal: () => void; closeModal: () => void }) => {
const { t } = useTranslation();
const brandname = t('brandname.brandname');
const { roles } = useFetchAvailableRoles();
const { principals } = useFetchAvailablePrincipals();
const rolesOptions = () => {
const array: SelectOptionProps[] = [];
roles.forEach((role) => {
array.push({
id: role.name,
value: role.name,
children: role.name,
description: role.description
});
});
return array;
};

const principalNameInitialState: IField = {
value: '',
isValid: false,
validated: 'default'
};

const principalRolesInitialState: IField = {
value: '',
isValid: false,
validated: 'default'
};

const [principalName, setPrincipalName] = useState<IField>(principalNameInitialState);
const [principalRolesField, setPrincipalRolesField] = useState<IField>(principalRolesInitialState);
const [selectedRoles, setSelectedRoles] = useState<string[]>([]);
const { onGrantAccess } = useGrantAccess(principalName.value, selectedRoles, props.submitModal);

useEffect(() => {
if (selectedRoles.length > 0) {
setPrincipalRolesField(principalRolesInitialState);
}
}, [selectedRoles]);

const handleSubmit = () => {
let isValid = true;
const trimmedPrincipalName = principalName.value.trim();
if (trimmedPrincipalName.length == 0) {
isValid = false;
setPrincipalName({
...principalName,
isValid: isValid,
invalidText: t('access-management.principals.modal-principal-name-is-required'),
validated: 'error'
});
} else if (principals.filter((p) => p.name == trimmedPrincipalName).length > 0) {
isValid = false;
setPrincipalName({
...principalName,
isValid: isValid,
invalidText: t('access-management.principals.modal-principal-exists', { name: trimmedPrincipalName }),
validated: 'error'
});
}
// validates permissions
if (selectedRoles.length == 0) {
isValid = false;
setPrincipalRolesField({
...principalRolesField,
isValid: isValid,
invalidText: t('access-management.principals.modal-roles-is-required'),
validated: 'error'
});
}

if (isValid) {
onGrantAccess();
onCloseModal();
}
};

const onCloseModal = () => {
props.closeModal();
setPrincipalName(principalNameInitialState);
setPrincipalRolesField(principalRolesInitialState);
setSelectedRoles([]);
};

const onSelectRoles = (value: string) => {
if (value && value !== 'no results') {
setSelectedRoles(
selectedRoles.includes(value)
? selectedRoles.filter((selection) => selection !== value)
: [...selectedRoles, value]
);
}
};

return (
<Modal
position={'top'}
tabIndex={0}
titleIconVariant={AddCircleOIcon}
variant={ModalVariant.small}
id={'grant-new-access-modal'}
className="pf-m-redhat-font"
isOpen={props.isModalOpen}
title={t('access-management.principals.modal-grant-title')}
description={t('access-management.principals.modal-grant-description', { brandname: brandname })}
onClose={onCloseModal}
aria-label={'principals-modal-grant-title'}
disableFocusTrap={true}
actions={[
<Button key={'Save'} aria-label={'Save'} variant={ButtonVariant.primary} onClick={handleSubmit}>
{t('common.actions.save')}
</Button>,
<Button key={'Cancel'} aria-label={'Cancel'} variant={ButtonVariant.link} onClick={onCloseModal}>
{t('common.actions.cancel')}
</Button>
]}
>
<Form
onSubmit={(e) => {
e.preventDefault();
}}
>
<FormGroup isRequired isInline label={t('access-management.principals.modal-principal-name')}>
<TextInput
validated={principalName.validated}
value={principalName.value}
type="text"
onChange={(_event, value) =>
formUtils.validateRequiredField(
value,
t('access-management.principals.modal-principal-name'),
setPrincipalName
)
}
aria-label="principal-name-input"
/>
{principalName.validated === 'error' && (
<FormHelperText>
<HelperText>
<HelperTextItem variant={'error'} icon={<ExclamationCircleIcon />}>
{principalName.invalidText}
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
<FormGroup fieldId="roles" isRequired isInline label={t('access-management.principals.modal-roles')}>
<SelectMultiWithChips
id="roles"
placeholder={t('access-management.principals.modal-roles-list-placeholder')}
options={rolesOptions()}
selection={selectedRoles}
onSelect={onSelectRoles}
onClear={() => setSelectedRoles([])}
/>
{principalRolesField.validated === 'error' && (
<FormHelperText>
<HelperText>
<HelperTextItem variant={'error'} icon={<ExclamationCircleIcon />}>
{principalRolesField.invalidText}
</HelperTextItem>
</HelperText>
</FormHelperText>
)}
</FormGroup>
</Form>
</Modal>
);
};

export { GrantNewAccess };
Loading

0 comments on commit f0204d4

Please sign in to comment.