mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Role Mappings] Warn when empty any or all rule role mappings are added (#189340)
Closes https://github.com/elastic/kibana/issues/187752 ## Summary Display a warning to users when a role mapping is created/updated to include empty `any` or `all` rules ### Screenshots <img width="1480" alt="image" src="https://github.com/user-attachments/assets/6cb7e505-95d9-43c6-b8b7-a1f9114cdcda"> ### Release notes Display a warning to users whenever role mappings with empty `any` or `all` rules are created or updated. --------- Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
parent
ce8f6ea882
commit
63a1cbe25e
5 changed files with 122 additions and 6 deletions
|
@ -12,6 +12,11 @@ export type {
|
|||
BuiltinESPrivileges,
|
||||
RawKibanaPrivileges,
|
||||
RoleMapping,
|
||||
RoleMappingRule,
|
||||
RoleMappingAllRule,
|
||||
RoleMappingAnyRule,
|
||||
RoleMappingExceptRule,
|
||||
RoleMappingFieldRule,
|
||||
RoleTemplate,
|
||||
StoredRoleTemplate,
|
||||
InvalidRoleTemplate,
|
||||
|
|
|
@ -40,4 +40,9 @@ export type {
|
|||
InvalidRoleTemplate,
|
||||
RoleTemplate,
|
||||
RoleMapping,
|
||||
RoleMappingRule,
|
||||
RoleMappingAllRule,
|
||||
RoleMappingAnyRule,
|
||||
RoleMappingExceptRule,
|
||||
RoleMappingFieldRule,
|
||||
} from './role_mapping';
|
||||
|
|
|
@ -5,23 +5,23 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
interface RoleMappingAnyRule {
|
||||
export interface RoleMappingAnyRule {
|
||||
any: RoleMappingRule[];
|
||||
}
|
||||
|
||||
interface RoleMappingAllRule {
|
||||
export interface RoleMappingAllRule {
|
||||
all: RoleMappingRule[];
|
||||
}
|
||||
|
||||
interface RoleMappingFieldRule {
|
||||
export interface RoleMappingFieldRule {
|
||||
field: Record<string, any>;
|
||||
}
|
||||
|
||||
interface RoleMappingExceptRule {
|
||||
export interface RoleMappingExceptRule {
|
||||
except: RoleMappingRule;
|
||||
}
|
||||
|
||||
type RoleMappingRule =
|
||||
export type RoleMappingRule =
|
||||
| RoleMappingAnyRule
|
||||
| RoleMappingAllRule
|
||||
| RoleMappingFieldRule
|
||||
|
|
|
@ -441,4 +441,43 @@ describe('EditRoleMappingPage', () => {
|
|||
expect(rulePanels).toHaveLength(1);
|
||||
expect(rulePanels.at(0).props().readOnly).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders a warning when empty any or all rules are present', async () => {
|
||||
const roleMappingsAPI = roleMappingsAPIClientMock.create();
|
||||
const securityFeaturesAPI = securityFeaturesAPIClientMock.create();
|
||||
roleMappingsAPI.saveRoleMapping.mockResolvedValue(null);
|
||||
roleMappingsAPI.getRoleMapping.mockResolvedValue({
|
||||
name: 'foo',
|
||||
role_templates: [
|
||||
{
|
||||
template: { id: 'foo' },
|
||||
},
|
||||
],
|
||||
enabled: true,
|
||||
rules: {
|
||||
all: [
|
||||
{
|
||||
field: {
|
||||
username: '*',
|
||||
},
|
||||
},
|
||||
{
|
||||
all: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
securityFeaturesAPI.checkFeatures.mockResolvedValue({
|
||||
canReadSecurity: true,
|
||||
hasCompatibleRealms: true,
|
||||
canUseInlineScripts: true,
|
||||
canUseStoredScripts: true,
|
||||
});
|
||||
|
||||
const wrapper = renderView(roleMappingsAPI, securityFeaturesAPI, 'foo');
|
||||
await nextTick();
|
||||
wrapper.update();
|
||||
|
||||
expect(findTestSubject(wrapper, 'emptyAnyOrAllRulesWarning')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import {
|
||||
EuiButton,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
|
@ -26,7 +27,12 @@ import type { PublicMethodsOf } from '@kbn/utility-types';
|
|||
import { MappingInfoPanel } from './mapping_info_panel';
|
||||
import { RuleEditorPanel } from './rule_editor_panel';
|
||||
import { validateRoleMappingForSave } from './services/role_mapping_validation';
|
||||
import type { RoleMapping } from '../../../../common';
|
||||
import type {
|
||||
RoleMapping,
|
||||
RoleMappingAllRule,
|
||||
RoleMappingAnyRule,
|
||||
RoleMappingRule,
|
||||
} from '../../../../common';
|
||||
import type { RolesAPIClient } from '../../roles';
|
||||
import type { SecurityFeaturesAPIClient } from '../../security_features';
|
||||
import {
|
||||
|
@ -176,6 +182,8 @@ export class EditRoleMappingPage extends Component<Props, State> {
|
|||
readOnly={this.props.readOnly}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
{this.getFormWarnings()}
|
||||
<EuiSpacer />
|
||||
{this.getFormButtons()}
|
||||
</EuiForm>
|
||||
</>
|
||||
|
@ -211,6 +219,65 @@ export class EditRoleMappingPage extends Component<Props, State> {
|
|||
);
|
||||
};
|
||||
|
||||
private isObject = (record?: any): record is object => {
|
||||
return typeof record === 'object' && record !== null;
|
||||
};
|
||||
|
||||
private isRoleMappingAnyRule = (obj: unknown): obj is RoleMappingAnyRule => {
|
||||
return this.isObject(obj) && 'any' in obj && Array.isArray(obj.any);
|
||||
};
|
||||
|
||||
private isRoleMappingAllRule = (obj: unknown): obj is RoleMappingAllRule => {
|
||||
return this.isObject(obj) && 'all' in obj && Array.isArray(obj.all);
|
||||
};
|
||||
|
||||
private checkEmptyAnyAllMappings = (obj: RoleMappingRule) => {
|
||||
const arrToCheck: RoleMappingRule[] = [obj];
|
||||
|
||||
while (arrToCheck.length > 0) {
|
||||
const currentObj = arrToCheck.pop();
|
||||
if (this.isObject(obj)) {
|
||||
for (const key in currentObj) {
|
||||
if (Object.hasOwn(currentObj, key)) {
|
||||
if (this.isRoleMappingAnyRule(currentObj)) {
|
||||
if (currentObj.any.length === 0) {
|
||||
return true;
|
||||
}
|
||||
arrToCheck.push(...currentObj.any);
|
||||
} else if (this.isRoleMappingAllRule(currentObj)) {
|
||||
if (currentObj.all.length === 0) {
|
||||
return true;
|
||||
}
|
||||
arrToCheck.push(...currentObj.all);
|
||||
} else if (this.isObject(currentObj[key as keyof RoleMappingRule])) {
|
||||
arrToCheck.push(currentObj[key as keyof RoleMappingRule] as RoleMappingRule);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
private getFormWarnings = () => {
|
||||
if (this.checkEmptyAnyAllMappings(this.state.roleMapping!.rules as RoleMappingRule)) {
|
||||
return (
|
||||
<EuiCallOut
|
||||
title="Warning"
|
||||
color="warning"
|
||||
iconType="alert"
|
||||
data-test-subj="emptyAnyOrAllRulesWarning"
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.editRoleMapping.emptyAnyAllMappingsWarning"
|
||||
defaultMessage="Role mapping rules contains empty 'any' or 'all' rules. These empty rule groups will always evaluate to true. Please proceed with caution"
|
||||
/>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
private getFormButtons = () => {
|
||||
if (this.props.readOnly === true) {
|
||||
return this.getReturnToRoleMappingListButton();
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue