diff --git a/src/core/public/saved_objects/saved_objects_client.test.ts b/src/core/public/saved_objects/saved_objects_client.test.ts index e633e00965c6a..0c34a16c68e99 100644 --- a/src/core/public/saved_objects/saved_objects_client.test.ts +++ b/src/core/public/saved_objects/saved_objects_client.test.ts @@ -448,23 +448,4 @@ describe('SavedObjectsClient', () => { `); }); }); - - it('maintains backwards compatibility by transforming http.fetch errors to be compatible with kfetch errors', () => { - const err = { - response: { ok: false, redirected: false, status: 409, statusText: 'Conflict' }, - body: 'response body', - }; - http.fetch.mockRejectedValue(err); - return expect(savedObjectsClient.get(doc.type, doc.id)).rejects.toMatchInlineSnapshot(` - Object { - "body": "response body", - "res": Object { - "ok": false, - "redirected": false, - "status": 409, - "statusText": "Conflict", - }, - } - `); - }); }); diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index dab98ee66cdb1..ccb23793a8534 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -465,11 +465,7 @@ export class SavedObjectsClient { * uses `{response: {status: number}}`. */ private savedObjectsFetch(path: string, { method, query, body }: HttpFetchOptions) { - return this.http.fetch(path, { method, query, body }).catch(err => { - const kfetchError = Object.assign(err, { res: err.response }); - delete kfetchError.response; - return Promise.reject(kfetchError); - }); + return this.http.fetch(path, { method, query, body }); } } diff --git a/x-pack/plugins/security/public/account_management/change_password/change_password.tsx b/x-pack/plugins/security/public/account_management/change_password/change_password.tsx index 263b0babd8882..f5ac5f3b21d2e 100644 --- a/x-pack/plugins/security/public/account_management/change_password/change_password.tsx +++ b/x-pack/plugins/security/public/account_management/change_password/change_password.tsx @@ -4,10 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { Component } from 'react'; -import { - // @ts-ignore - EuiDescribedFormGroup, -} from '@elastic/eui'; +import { EuiDescribedFormGroup } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { NotificationsSetup } from 'src/core/public'; import { AuthenticatedUser, canUserChangePassword } from '../../../common/model'; diff --git a/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx b/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx index bf7991d9c4b99..9cbbc242e8400 100644 --- a/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx +++ b/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { - // @ts-ignore - EuiDescribedFormGroup, - EuiFormRow, - EuiText, -} from '@elastic/eui'; +import { EuiDescribedFormGroup, EuiFormRow, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { AuthenticatedUser } from '../../../common/model'; diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx index 5b6463c99ab27..02af6bfbafa7e 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/mapping_info_panel/mapping_info_panel.tsx @@ -10,7 +10,6 @@ import { EuiTitle, EuiText, EuiSpacer, - // @ts-ignore EuiDescribedFormGroup, EuiFormRow, EuiFieldText, diff --git a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx index 4f5808be7763d..e7a9149513d20 100644 --- a/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx +++ b/x-pack/plugins/security/public/management/role_mappings/edit_role_mapping/rule_editor_panel/json_rule_editor.tsx @@ -8,15 +8,7 @@ import React, { useState, Fragment } from 'react'; import 'brace/mode/json'; import 'brace/theme/github'; -import { - // @ts-ignore - EuiCodeEditor, - EuiFormRow, - EuiButton, - EuiSpacer, - EuiLink, - EuiText, -} from '@elastic/eui'; +import { EuiCodeEditor, EuiFormRow, EuiButton, EuiSpacer, EuiLink, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { DocumentationLinksService } from '../../documentation_links'; diff --git a/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.tsx b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.tsx index 416dd7f6c4e5c..01af7cb4509f6 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/collapsible_panel/collapsible_panel.tsx @@ -50,7 +50,6 @@ export class CollapsiblePanel extends Component { public getTitle = () => { return ( - // @ts-ignore diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx index 8248a85ab55de..2b3d7c811f6de 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.test.tsx @@ -128,7 +128,7 @@ function getProps({ spacesEnabled = true, }: { action: 'edit' | 'clone'; - role: Role; + role?: Role; canManageSpaces?: boolean; spacesEnabled?: boolean; }) { @@ -167,7 +167,7 @@ function getProps({ return { action, - roleName: role.name, + roleName: role?.name, license, http, indexPatterns, @@ -238,18 +238,7 @@ describe('', () => { }); it('can render when creating a new role', async () => { - const wrapper = mountWithIntl( - - ); + const wrapper = mountWithIntl(); await act(async () => { await nextTick(); @@ -411,17 +400,7 @@ describe('', () => { it('can render when creating a new role', async () => { const wrapper = mountWithIntl( - + ); await act(async () => { @@ -527,4 +506,47 @@ describe('', () => { expectReadOnlyFormButtons(wrapper); }); }); + + it('can render if features are not available', async () => { + const { http } = coreMock.createStart(); + http.get.mockImplementation(async path => { + if (path === '/api/features') { + const error = { response: { status: 404 } }; + throw error; + } + + if (path === '/api/spaces/space') { + return buildSpaces(); + } + }); + + const wrapper = mountWithIntl(); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); + + it('can render if index patterns are not available', async () => { + const indexPatterns = dataPluginMock.createStartContract().indexPatterns; + indexPatterns.getTitles = jest.fn().mockRejectedValue({ response: { status: 403 } }); + + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + + expect(wrapper.find(SpaceAwarePrivilegeSection)).toHaveLength(1); + expect(wrapper.find('[data-test-subj="userCannotManageSpacesCallout"]')).toHaveLength(0); + expectSaveFormButtons(wrapper); + }); }); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx index e123bb987b8d0..33e69a68ca896 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/edit_role_page.tsx @@ -96,12 +96,30 @@ function useRunAsUsers( function useIndexPatternsTitles( indexPatterns: IndexPatternsContract, - fatalErrors: FatalErrorsSetup + fatalErrors: FatalErrorsSetup, + notifications: NotificationsStart ) { const [indexPatternsTitles, setIndexPatternsTitles] = useState(null); useEffect(() => { - indexPatterns.getTitles().then(setIndexPatternsTitles, err => fatalErrors.add(err)); - }, [fatalErrors, indexPatterns]); + indexPatterns + .getTitles() + .catch((err: IHttpFetchError) => { + // If user doesn't have access to the index patterns they still should be able to create new + // or edit existing role. + if (err.response?.status === 403) { + notifications.toasts.addDanger({ + title: i18n.translate('xpack.security.management.roles.noIndexPatternsPermission', { + defaultMessage: 'You need permission to access the list of available index patterns.', + }), + }); + return []; + } + + fatalErrors.add(err); + throw err; + }) + .then(setIndexPatternsTitles); + }, [fatalErrors, indexPatterns, notifications]); return indexPatternsTitles; } @@ -213,18 +231,25 @@ function useSpaces(http: HttpStart, fatalErrors: FatalErrorsSetup, spacesEnabled function useFeatures(http: HttpStart, fatalErrors: FatalErrorsSetup) { const [features, setFeatures] = useState(null); useEffect(() => { - http.get('/api/features').then( - fetchedFeatures => setFeatures(fetchedFeatures), - (err: IHttpFetchError) => { - // TODO: This check can be removed once all of these `resolve` entries are moved out of Angular and into the React app. + http + .get('/api/features') + .catch((err: IHttpFetchError) => { + // Currently, the `/api/features` endpoint effectively requires the "Global All" kibana privilege (e.g., what + // the `kibana_user` grants), because it returns information about all registered features (#35841). It's + // possible that a user with `manage_security` will attempt to visit the role management page without the + // correct Kibana privileges. If that's the case, then they receive a partial view of the role, and the UI does + // not allow them to make changes to that role's kibana privileges. When this user visits the edit role page, + // this API endpoint will throw a 404, which causes view to fail completely. So we instead attempt to detect the + // 404 here, and respond in a way that still allows the UI to render itself. const unauthorizedForFeatures = err.response?.status === 404; if (unauthorizedForFeatures) { return []; } fatalErrors.add(err); - } - ); + throw err; + }) + .then(setFeatures); }, [http, fatalErrors]); return features; @@ -256,7 +281,7 @@ export const EditRolePage: FunctionComponent = ({ const [formError, setFormError] = useState(null); const runAsUsers = useRunAsUsers(userAPIClient, fatalErrors); - const indexPatternsTitles = useIndexPatternsTitles(indexPatterns, fatalErrors); + const indexPatternsTitles = useIndexPatternsTitles(indexPatterns, fatalErrors, notifications); const privileges = usePrivileges(privilegesAPIClient, fatalErrors); const spaces = useSpaces(http, fatalErrors, spacesEnabled); const features = useFeatures(http, fatalErrors); diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx index 166a11aa32838..96249ccf3ff87 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/elasticsearch_privileges.tsx @@ -7,7 +7,6 @@ import { EuiButton, EuiComboBox, - // @ts-ignore EuiDescribedFormGroup, EuiFormRow, EuiHorizontalRule, diff --git a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx index 0cc2639314f69..15e0367c2b6dc 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/privileges/es/index_privilege_form.tsx @@ -164,7 +164,6 @@ export class IndexPrivilegeForm extends Component { {!isReadOnlyRole && ( { - // @ts-ignore missing "compressed" prop definition { } return ( - // @ts-ignore {!this.props.isReadOnlyRole && ( { - // @ts-ignore missing "compressed" proptype { const availablePrivileges = Object.values(rankedFeaturePrivileges)[0]; return ( - // @ts-ignore missing responsive from typedef { }); return ( - // @ts-ignore missing name from typedef { ]; return ( - // @ts-ignore missing rowProps from typedef { return { className: item.feature.isBase ? 'secPrivilegeMatrix__row--isBasePrivilege' : '', diff --git a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx index 587d0b3917604..bb7a6db97f7c8 100644 --- a/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx +++ b/x-pack/plugins/security/public/management/roles/edit_role/spaces_popover_list/spaces_popover_list.tsx @@ -146,7 +146,6 @@ export class SpacesPopoverList extends Component { return (
{ - // @ts-ignore onSearch isn't defined on the type