Skip to content

Commit a6af6a9

Browse files
committed
[#599] Security config update
1 parent 2430585 commit a6af6a9

File tree

9 files changed

+208
-4
lines changed

9 files changed

+208
-4
lines changed

cypress/e2e/6_cache-expiration-config-edition.cy.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ describe('Cache Expiration', () => {
5353
cy.get('[data-cy=toggle-maxidleUnitSelector]').click();
5454
cy.get('[data-cy=maxidleUnitSelector-option-minutes]').click();
5555
cy.get('[data-cy=saveConfigButton-expiration]').click();
56+
cy.get('[data-cy=saveConfigButton-expiration]').click();
5657
cy.contains(`Updated ${cacheName} cache: expiration.lifespan configured successfully`);
5758
cy.contains(`Updated ${cacheName} cache: expiration.max-idle configured successfully`);
5859
cy.get('[data-cy=backButton-expiration]').click();

cypress/e2e/6_cache-indexed-config-edition.cy.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { CONF_MUTABLE_INDEXING_INDEXED_ENTITIES } from '../../src/services/cache
33
describe('Indexed Cache Update', () => {
44
before(() => {
55
cy.cleanupTest(Cypress.env('username'), Cypress.env('password'),
6-
`/caches/indexed-cache?action=set-mutable-attribute&attribute-name=${CONF_MUTABLE_INDEXING_INDEXED_ENTITIES}&attribute-value=['org.infinispan.Person']`,
6+
`/caches/indexed-cache?action=set-mutable-attribute&attribute-name=${CONF_MUTABLE_INDEXING_INDEXED_ENTITIES}&attribute-value='org.infinispan.Person'`,
77
'POST');
88
});
99

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import {
2+
CONF_MUTABLE_SECURITY_AUTHORIZATION_ROLES
3+
} from '../../src/services/cacheConfigUtils';
4+
5+
describe('RBAC Cache Update', () => {
6+
before(() => {
7+
cy.cleanupTest(Cypress.env('username'), Cypress.env('password'),
8+
`/caches/a-rbac-test-cache?action=set-mutable-attribute&attribute-name=${CONF_MUTABLE_SECURITY_AUTHORIZATION_ROLES}&attribute-value=observer admin monitor`,
9+
'POST');
10+
});
11+
12+
it('successfully displays and updates roles', () => {
13+
cy.login(Cypress.env('username'), Cypress.env('password'), '/cache/a-rbac-test-cache');
14+
cy.get('[data-cy=cacheConfigurationTab]').click();
15+
cy.contains("observer").should('exist');
16+
cy.contains("admin").should('exist');
17+
cy.contains("monitor").should('exist');
18+
cy.contains("deployer").should('not.exist');
19+
cy.get('[data-cy=detailCacheActions]').click();
20+
cy.get('[data-cy=manageConfigEditionLink]').click();
21+
cy.get('[data-cy=nav-item-Security]').click();
22+
cy.contains("Security settings");
23+
cy.get('[data-cy=menu-toogle-rolesSelector]').click().type('deployer').type('{enter}');
24+
cy.get('[data-cy=menu-toogle-rolesSelector]').click()
25+
cy.get('[data-cy=saveConfigButton-security]').click();
26+
cy.get('[data-cy=backButton-security]').click();
27+
cy.contains('Updated a-rbac-test-cache cache: security.authorization.roles configured successfully');
28+
cy.get('[data-cy=cacheConfigurationTab]').click();
29+
cy.contains("observer").should('exist');
30+
cy.contains("admin").should('exist');
31+
cy.contains("monitor").should('exist');
32+
cy.contains("deployer").should('exist');
33+
});
34+
35+
it('tab is invisible for not indexed caches', () => {
36+
cy.login(Cypress.env('username'), Cypress.env('password'), '/cache/default');
37+
cy.get('[data-cy=nav-item-Indexed]').should('not.exist');
38+
});
39+
});

src/app/Caches/Configuration/EditConfiguration.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { useCacheDetail } from '@app/services/cachesHook';
1111
import { TableErrorState } from '@app/Common/TableErrorState';
1212
import { TableLoadingState } from '@app/Common/TableLoadingState';
1313
import { IndexedConfigEdition } from '@app/Caches/Configuration/Features/IndexedConfigEdition';
14+
import { SecurityConfigEdition } from '@app/Caches/Configuration/Features/SecurityConfigEdition';
1415

1516
interface EditConfigTab {
1617
key: string;
@@ -40,6 +41,9 @@ const EditConfiguration = () => {
4041
if (cache.features?.indexed) {
4142
cacheConfigTabs.push({ name: t('caches.edit-configuration.tab-indexed'), key: 'indexed', eventKey: 2 });
4243
}
44+
if (cache.features?.secured) {
45+
cacheConfigTabs.push({ name: t('caches.edit-configuration.tab-secured'), key: 'secured', eventKey: 3 });
46+
}
4347
setTabs(cacheConfigTabs);
4448
}, [cache]);
4549

@@ -78,6 +82,7 @@ const EditConfiguration = () => {
7882
{activeTabKey == 0 && <ExpirationConfigEdition />}
7983
{activeTabKey == 1 && <BoundedConfigEdition />}
8084
{activeTabKey == 2 && <IndexedConfigEdition />}
85+
{activeTabKey == 3 && <SecurityConfigEdition />}
8186
</CardBody>
8287
</Card>
8388
</React.Fragment>
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { useTranslation } from 'react-i18next';
2+
import { useApiAlert } from '@utils/useApiAlert';
3+
import { useParams } from 'react-router-dom';
4+
import { useFetchEditableConfiguration } from '@app/services/configHook';
5+
import * as React from 'react';
6+
import { useEffect, useState } from 'react';
7+
import {
8+
Alert,
9+
Content,
10+
Form,
11+
FormGroup,
12+
FormHelperText,
13+
FormSection,
14+
Grid,
15+
GridItem,
16+
HelperText,
17+
HelperTextItem
18+
} from '@patternfly/react-core';
19+
import { ExclamationCircleIcon } from '@patternfly/react-icons';
20+
import { SelectMultiWithChips } from '@app/Common/SelectMultiWithChips';
21+
import { selectOptionPropsFromArray } from '@utils/selectOptionPropsCreator';
22+
import { TabsToolbar } from '@app/Caches/Configuration/Features/TabsToolbar';
23+
import { TableLoadingState } from '@app/Common/TableLoadingState';
24+
import { ConsoleServices } from '@services/ConsoleServices';
25+
import { CONF_MUTABLE_SECURITY_AUTHORIZATION_ROLES } from '@services/cacheConfigUtils';
26+
import { TableErrorState } from '@app/Common/TableErrorState';
27+
import { useFetchAvailableRolesNames } from '@app/services/rolesHook';
28+
29+
const SecurityConfigEdition = () => {
30+
const { t } = useTranslation();
31+
const brandname = t('brandname.brandname');
32+
const { addAlert } = useApiAlert();
33+
const cacheName = useParams()['cacheName'] as string;
34+
const { loadingConfig, errorConfig, editableConfig } = useFetchEditableConfiguration(cacheName);
35+
const { availableRoleNames, loading: loadingRoles, error: rolesError } = useFetchAvailableRolesNames();
36+
const [roles, setRoles] = useState<string[]>([]);
37+
const [validEntity, setvalidEntity] = useState<'success' | 'error' | 'default'>('default');
38+
const [updateConfigError, setUpdateConfigError] = useState<string>('');
39+
40+
const onSelectRoles = (selection) => {
41+
if (roles.includes(selection)) setRoles(roles.filter((role) => role !== selection));
42+
else setRoles([...roles, selection]);
43+
};
44+
45+
useEffect(() => {
46+
if (!loadingConfig && errorConfig.length == 0 && editableConfig) {
47+
setActualValues();
48+
}
49+
}, [loadingConfig, errorConfig, editableConfig]);
50+
51+
const updateSecurity = () => {
52+
if (roles.length == 0) {
53+
setvalidEntity('error');
54+
return;
55+
}
56+
57+
setvalidEntity('success');
58+
59+
ConsoleServices.caches()
60+
.setConfigAttribute(cacheName, CONF_MUTABLE_SECURITY_AUTHORIZATION_ROLES, roles.join(' '))
61+
.then((actionResponse) => {
62+
if (actionResponse.success) {
63+
addAlert(actionResponse);
64+
} else {
65+
setUpdateConfigError(actionResponse.message);
66+
}
67+
});
68+
};
69+
70+
const setActualValues = () => {
71+
if (!editableConfig) {
72+
return;
73+
}
74+
setRoles(editableConfig.securityAuthorizationRoles);
75+
};
76+
77+
const displayError = () => {
78+
if (updateConfigError.length == 0) {
79+
return <></>;
80+
}
81+
82+
return (
83+
<GridItem span={12}>
84+
<Alert
85+
variant="danger"
86+
isInline
87+
title={t('caches.edit-configuration.security-config-error', { error: updateConfigError })}
88+
/>
89+
</GridItem>
90+
);
91+
};
92+
93+
if (loadingRoles || loadingConfig) {
94+
return <TableLoadingState message={t('common.loading')} />;
95+
}
96+
97+
if (rolesError.length != 0) {
98+
return <TableErrorState error={rolesError} />;
99+
}
100+
101+
return (
102+
<Form
103+
isWidthLimited
104+
onSubmit={(e) => {
105+
e.preventDefault();
106+
}}
107+
>
108+
<FormSection title={t('caches.edit-configuration.security-title')}>
109+
<Grid hasGutter md={6}>
110+
<GridItem span={12}>
111+
<Content>
112+
{t('caches.edit-configuration.security-description', {
113+
brandname: brandname
114+
})}
115+
</Content>
116+
</GridItem>
117+
</Grid>
118+
119+
{displayError()}
120+
<FormGroup
121+
isRequired
122+
label={t('caches.edit-configuration.security-authz-roles')}
123+
fieldId="security-authz-roles"
124+
>
125+
<SelectMultiWithChips
126+
id="rolesSelector"
127+
placeholder={'Select an role'}
128+
options={selectOptionPropsFromArray(availableRoleNames)}
129+
onSelect={onSelectRoles}
130+
onClear={() => setRoles([])}
131+
selection={roles}
132+
/>
133+
134+
{validEntity === 'error' && (
135+
<FormHelperText>
136+
<HelperText>
137+
<HelperTextItem variant={validEntity} icon={<ExclamationCircleIcon />}>
138+
{t('caches.edit-configuration.security-authz-roles-helper-invalid')}
139+
</HelperTextItem>
140+
</HelperText>
141+
</FormHelperText>
142+
)}
143+
</FormGroup>
144+
{<TabsToolbar id="security" saveAction={updateSecurity} />}
145+
</FormSection>
146+
</Form>
147+
);
148+
};
149+
export { SecurityConfigEdition };

src/app/assets/languages/en.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -777,7 +777,13 @@
777777
"indexed-title": "Indexing settings",
778778
"index-storage-entity": "Entities to index",
779779
"index-storage-description": "Specify the fully qualified class names of the entities you want {{brandname}} to include when building the index.",
780-
"index-storage-entity-helper-invalid": "You must add at least one entity."
780+
"index-storage-entity-helper-invalid": "You must add at least one entity.",
781+
"tab-secured": "Security",
782+
"security-title": "Security settings",
783+
"security-description": "Update roles in this cache and grant or deny access to {{brandname}} cache operations.",
784+
"security-authz-roles": "Roles",
785+
"security-authz-roles-helper-invalid": "You must add at least one role.",
786+
"security-config-error": "Unable to upgrade roles in this cache: {{error}}"
781787
},
782788
"backups": {
783789
"title": "Backups management",

src/services/cacheConfigUtils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export const CONF_MUTABLE_EXPIRATION_LIFESPAN = 'expiration.lifespan';
1919
export const CONF_MUTABLE_MEMORY_MAX_SIZE = 'memory.max-size';
2020
export const CONF_MUTABLE_MEMORY_MAX_COUNT = 'memory.max-count';
2121
export const CONF_MUTABLE_INDEXING_INDEXED_ENTITIES = 'indexing.indexed-entities';
22+
export const CONF_MUTABLE_SECURITY_AUTHORIZATION_ROLES = 'security.authorization.roles';
2223

2324
/**
2425
* Utility class to map cache configuration

src/services/cacheService.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ import {
66
CONF_MUTABLE_EXPIRATION_MAXIDLE,
77
CONF_MUTABLE_INDEXING_INDEXED_ENTITIES,
88
CONF_MUTABLE_MEMORY_MAX_COUNT,
9-
CONF_MUTABLE_MEMORY_MAX_SIZE
9+
CONF_MUTABLE_MEMORY_MAX_SIZE,
10+
CONF_MUTABLE_SECURITY_AUTHORIZATION_ROLES
1011
} from '@services/cacheConfigUtils';
1112
import { ContentTypeHeaderMapper } from '@services/contentTypeHeaderMapper';
1213
import { CacheRequestResponseMapper } from '@services/cacheRequestResponseMapper';
@@ -706,7 +707,8 @@ export class CacheService {
706707
lifespan: data[CONF_MUTABLE_EXPIRATION_LIFESPAN].value,
707708
memoryMaxSize: this.extractValueOrUndefined(CONF_MUTABLE_MEMORY_MAX_SIZE, data),
708709
memoryMaxCount: this.extractValueOrUndefined(CONF_MUTABLE_MEMORY_MAX_COUNT, data),
709-
indexedEntities: this.extractValueOrEmpty(CONF_MUTABLE_INDEXING_INDEXED_ENTITIES, data)
710+
indexedEntities: this.extractValueOrEmpty(CONF_MUTABLE_INDEXING_INDEXED_ENTITIES, data),
711+
securityAuthorizationRoles: this.extractValueOrEmpty(CONF_MUTABLE_SECURITY_AUTHORIZATION_ROLES, data)
710712
};
711713
});
712714
}

src/types/InfinispanTypes.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,4 +562,5 @@ interface EditableConfig {
562562
memoryMaxSize?: string;
563563
memoryMaxCount?: number;
564564
indexedEntities: string[];
565+
securityAuthorizationRoles: string[];
565566
}

0 commit comments

Comments
 (0)