From bbea19ffb3bc97173475e3cc3c72c280d01dffa1 Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Wed, 28 Oct 2020 20:59:14 +0000 Subject: [PATCH 01/15] Account Management: Create API keys --- package.json | 2 +- x-pack/package.json | 2 +- .../account_details_page.tsx | 33 ++ .../change_password.tsx | 0 .../index.ts | 2 + .../personal_info.tsx | 37 +- .../account_management_app.ts | 54 -- .../account_management_app.tsx | 168 ++++++ .../account_management_page.test.tsx | 140 ----- .../account_management_page.tsx | 69 --- .../api_keys_page/api_keys_empty_prompt.tsx | 109 ++++ .../api_keys_page/api_keys_page.tsx | 156 ++++++ .../api_keys_page/api_keys_table.tsx | 139 +++++ .../api_keys_page/create_api_key_flyout.tsx | 273 ++++++++++ .../account_management/api_keys_page/index.ts | 11 + .../invalidate_api_key_modal.tsx | 100 ++++ .../components/breadcrumb.tsx | 109 ++++ .../components/confirm_modal.tsx | 75 +++ .../field_text_with_copy_button.tsx | 34 ++ .../components/form_flyout.tsx | 82 +++ .../components/tabbed_routes.tsx | 43 ++ .../account_management/components/use_form.ts | 235 ++++++++ .../account_management/personal_info/index.ts | 7 - .../api_keys/api_keys_api_client.ts | 41 +- .../security/server/routes/api_keys/create.ts | 42 ++ .../security/server/routes/api_keys/index.ts | 2 + .../server/routes/views/account_management.ts | 8 + yarn.lock | 507 +++++++++++++++--- 28 files changed, 2097 insertions(+), 383 deletions(-) create mode 100644 x-pack/plugins/security/public/account_management/account_details_page/account_details_page.tsx rename x-pack/plugins/security/public/account_management/{change_password => account_details_page}/change_password.tsx (100%) rename x-pack/plugins/security/public/account_management/{change_password => account_details_page}/index.ts (63%) rename x-pack/plugins/security/public/account_management/{personal_info => account_details_page}/personal_info.tsx (59%) delete mode 100644 x-pack/plugins/security/public/account_management/account_management_app.ts create mode 100644 x-pack/plugins/security/public/account_management/account_management_app.tsx delete mode 100644 x-pack/plugins/security/public/account_management/account_management_page.test.tsx delete mode 100644 x-pack/plugins/security/public/account_management/account_management_page.tsx create mode 100644 x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx create mode 100644 x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx create mode 100644 x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx create mode 100644 x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx create mode 100644 x-pack/plugins/security/public/account_management/api_keys_page/index.ts create mode 100644 x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx create mode 100644 x-pack/plugins/security/public/account_management/components/breadcrumb.tsx create mode 100644 x-pack/plugins/security/public/account_management/components/confirm_modal.tsx create mode 100644 x-pack/plugins/security/public/account_management/components/field_text_with_copy_button.tsx create mode 100644 x-pack/plugins/security/public/account_management/components/form_flyout.tsx create mode 100644 x-pack/plugins/security/public/account_management/components/tabbed_routes.tsx create mode 100644 x-pack/plugins/security/public/account_management/components/use_form.ts delete mode 100644 x-pack/plugins/security/public/account_management/personal_info/index.ts create mode 100644 x-pack/plugins/security/server/routes/api_keys/create.ts diff --git a/package.json b/package.json index 3a2d13fd5ef3bc..7c0b073fda0f23 100644 --- a/package.json +++ b/package.json @@ -199,7 +199,7 @@ "react-dom": "^16.12.0", "react-input-range": "^1.3.0", "react-router": "^5.2.0", - "react-use": "^13.27.0", + "react-use": "^15.3.4", "redux-actions": "^2.6.5", "redux-thunk": "^2.3.0", "regenerator-runtime": "^0.13.3", diff --git a/x-pack/package.json b/x-pack/package.json index ec4388c0b8b7d0..778d69854489cf 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -228,7 +228,7 @@ "react-syntax-highlighter": "^5.7.0", "react-test-renderer": "^16.12.0", "react-tiny-virtual-list": "^2.2.0", - "react-use": "^13.27.0", + "react-use": "^15.3.4", "react-virtualized": "^9.21.2", "react-vis": "^1.8.1", "react-visibility-sensor": "^5.1.1", diff --git a/x-pack/plugins/security/public/account_management/account_details_page/account_details_page.tsx b/x-pack/plugins/security/public/account_management/account_details_page/account_details_page.tsx new file mode 100644 index 00000000000000..0d446a631a37df --- /dev/null +++ b/x-pack/plugins/security/public/account_management/account_details_page/account_details_page.tsx @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { ChangePassword } from './change_password'; +import { PersonalInfo } from './personal_info'; +import { AuthenticatedUser } from '../../../common/model'; +import { UserAPIClient } from '../../management'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { NotificationsSetup } from '../../../../../../src/core/public'; + +export interface AccountDetailsPageProps { + user: AuthenticatedUser; + notifications: NotificationsSetup; +} + +export const AccountDetailsPage: FunctionComponent = ({ + user, + notifications, +}) => { + const { services } = useKibana(); + const userAPIClient = new UserAPIClient(services.http!); + + return ( + <> + + + + ); +}; diff --git a/x-pack/plugins/security/public/account_management/change_password/change_password.tsx b/x-pack/plugins/security/public/account_management/account_details_page/change_password.tsx similarity index 100% rename from x-pack/plugins/security/public/account_management/change_password/change_password.tsx rename to x-pack/plugins/security/public/account_management/account_details_page/change_password.tsx diff --git a/x-pack/plugins/security/public/account_management/change_password/index.ts b/x-pack/plugins/security/public/account_management/account_details_page/index.ts similarity index 63% rename from x-pack/plugins/security/public/account_management/change_password/index.ts rename to x-pack/plugins/security/public/account_management/account_details_page/index.ts index ccd810bb814c00..a1b31254e87cb2 100644 --- a/x-pack/plugins/security/public/account_management/change_password/index.ts +++ b/x-pack/plugins/security/public/account_management/account_details_page/index.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export { AccountDetailsPage as default } from './account_details_page'; // eslint-disable-line import/no-default-export export { ChangePassword } from './change_password'; +export { PersonalInfo } from './personal_info'; diff --git a/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx b/x-pack/plugins/security/public/account_management/account_details_page/personal_info.tsx similarity index 59% rename from x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx rename to x-pack/plugins/security/public/account_management/account_details_page/personal_info.tsx index 9cbbc242e8400e..a46b10ea997b8f 100644 --- a/x-pack/plugins/security/public/account_management/personal_info/personal_info.tsx +++ b/x-pack/plugins/security/public/account_management/account_details_page/personal_info.tsx @@ -4,7 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { EuiDescribedFormGroup, EuiFormRow, EuiText } from '@elastic/eui'; +import { + EuiDescribedFormGroup, + EuiDescriptionList, + EuiDescriptionListTitle, + EuiDescriptionListDescription, +} from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { AuthenticatedUser } from '../../../common/model'; @@ -31,23 +36,19 @@ export const PersonalInfo = (props: Props) => { /> } > - - -
-
- {props.user.username} -
-
- {props.user.email || ( - - )} -
-
-
-
+ + + {props.user.username} + + + {props.user.email || ( + + )} + + ); }; diff --git a/x-pack/plugins/security/public/account_management/account_management_app.ts b/x-pack/plugins/security/public/account_management/account_management_app.ts deleted file mode 100644 index 0bb7785259c0ea..00000000000000 --- a/x-pack/plugins/security/public/account_management/account_management_app.ts +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { i18n } from '@kbn/i18n'; -import { - ApplicationSetup, - AppMountParameters, - AppNavLinkStatus, - StartServicesAccessor, -} from '../../../../../src/core/public'; -import { AuthenticationServiceSetup } from '../authentication'; - -interface CreateDeps { - application: ApplicationSetup; - authc: AuthenticationServiceSetup; - getStartServices: StartServicesAccessor; -} - -export const accountManagementApp = Object.freeze({ - id: 'security_account', - create({ application, authc, getStartServices }: CreateDeps) { - const title = i18n.translate('xpack.security.account.breadcrumb', { - defaultMessage: 'Account Management', - }); - application.register({ - id: this.id, - title, - navLinkStatus: AppNavLinkStatus.hidden, - appRoute: '/security/account', - async mount({ element }: AppMountParameters) { - const [ - [coreStart], - { renderAccountManagementPage }, - { UserAPIClient }, - ] = await Promise.all([ - getStartServices(), - import('./account_management_page'), - import('../management'), - ]); - - coreStart.chrome.setBreadcrumbs([{ text: title }]); - - return renderAccountManagementPage(coreStart.i18n, element, { - authc, - notifications: coreStart.notifications, - userAPIClient: new UserAPIClient(coreStart.http), - }); - }, - }); - }, -}); diff --git a/x-pack/plugins/security/public/account_management/account_management_app.tsx b/x-pack/plugins/security/public/account_management/account_management_app.tsx new file mode 100644 index 00000000000000..03b7f7be1e7ab9 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/account_management_app.tsx @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { lazy, Suspense, FunctionComponent } from 'react'; +import ReactDOM from 'react-dom'; +import { Router, useHistory } from 'react-router-dom'; +import { useAsync } from 'react-use'; +import { i18n } from '@kbn/i18n'; +import { + EuiAvatar, + EuiFlexGroup, + EuiFlexItem, + EuiLoadingSpinner, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { + ApplicationSetup, + AppMountParameters, + AppNavLinkStatus, + StartServicesAccessor, + NotificationsSetup, +} from '../../../../../src/core/public'; +import { + createKibanaReactContext, + reactRouterNavigate, +} from '../../../../../src/plugins/kibana_react/public'; +import { getUserDisplayName } from '../../common/model'; +import { AuthenticationServiceSetup } from '../authentication'; +import { TabbedRoutes } from './components/tabbed_routes'; +import { Breadcrumb } from './components/breadcrumb'; + +interface CreateDeps { + application: ApplicationSetup; + authc: AuthenticationServiceSetup; + getStartServices: StartServicesAccessor; +} + +export const accountManagementApp = Object.freeze({ + id: 'security_account', + create({ application, authc, getStartServices }: CreateDeps) { + const title = i18n.translate('xpack.security.account.breadcrumb', { + defaultMessage: 'Account Management', + }); + + application.register({ + id: this.id, + title, + navLinkStatus: AppNavLinkStatus.hidden, + appRoute: '/security/account', + async mount({ element, history }: AppMountParameters) { + const [coreStart] = await getStartServices(); + + const { Provider: KibanaProvider } = createKibanaReactContext(coreStart); + const { Context: IntlProvider } = coreStart.i18n; + + ReactDOM.render( + + + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); + }, + }); + }, +}); + +export interface AccountManagementProps { + authc: AuthenticationServiceSetup; + notifications: NotificationsSetup; +} + +export const AccountManagement: FunctionComponent = ({ + authc, + notifications, +}) => { + const history = useHistory(); + const userState = useAsync(authc.getCurrentUser, [authc]); + const AccountDetailsPage = lazy(() => import('./account_details_page')); + const ApiKeysPage = lazy(() => import('./api_keys_page')); + + if (!userState.value) { + return null; + } + + const displayName = getUserDisplayName(userState.value); + + return ( + + + + + + + + + + + +

{displayName}

+
+ {userState.value.email} +
+
+
+
+ + }> + + + ), + }, + { + id: 'api-keys', + path: '/api-keys', + to: 'api-keys', + name: i18n.translate('xpack.security.accountManagement.ApiKeysTab', { + defaultMessage: 'API Keys', + }), + content: ( + + }> + + + + ), + }, + ]} + /> + +
+
+
+ ); +}; diff --git a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx b/x-pack/plugins/security/public/account_management/account_management_page.test.tsx deleted file mode 100644 index b677f6a1fe8bba..00000000000000 --- a/x-pack/plugins/security/public/account_management/account_management_page.test.tsx +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { act } from '@testing-library/react'; -import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { AuthenticatedUser } from '../../common/model'; -import { AccountManagementPage } from './account_management_page'; -import { coreMock } from 'src/core/public/mocks'; -import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; -import { securityMock } from '../mocks'; -import { userAPIClientMock } from '../management/users/index.mock'; - -interface Options { - withFullName?: boolean; - withEmail?: boolean; - realm?: string; -} -const createUser = ({ withFullName = true, withEmail = true, realm = 'native' }: Options = {}) => { - return mockAuthenticatedUser({ - full_name: withFullName ? 'Casey Smith' : '', - username: 'csmith', - email: withEmail ? 'csmith@domain.com' : '', - roles: [], - authentication_realm: { - type: realm, - name: realm, - }, - lookup_realm: { - type: realm, - name: realm, - }, - }); -}; - -function getSecuritySetupMock({ currentUser }: { currentUser: AuthenticatedUser }) { - const securitySetupMock = securityMock.createSetup(); - securitySetupMock.authc.getCurrentUser.mockResolvedValue(currentUser); - return securitySetupMock; -} - -describe('', () => { - it(`displays users full name, username, and email address`, async () => { - const user = createUser(); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( - user.full_name - ); - expect(wrapper.find('[data-test-subj="username"]').text()).toEqual(user.username); - expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); - }); - - it(`displays username when full_name is not provided`, async () => { - const user = createUser({ withFullName: false }); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(user.username); - }); - - it(`displays a placeholder when no email address is provided`, async () => { - const user = createUser({ withEmail: false }); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('[data-test-subj="email"]').text()).toEqual('no email address'); - }); - - it(`displays change password form for users in the native realm`, async () => { - const user = createUser(); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('EuiFieldPassword[data-test-subj="currentPassword"]')).toHaveLength(1); - expect(wrapper.find('EuiFieldPassword[data-test-subj="newPassword"]')).toHaveLength(1); - }); - - it(`does not display change password form for users in the saml realm`, async () => { - const user = createUser({ realm: 'saml' }); - const wrapper = mountWithIntl( - - ); - - await act(async () => { - await nextTick(); - wrapper.update(); - }); - - expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(0); - expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(0); - }); -}); diff --git a/x-pack/plugins/security/public/account_management/account_management_page.tsx b/x-pack/plugins/security/public/account_management/account_management_page.tsx deleted file mode 100644 index 2c870bf788ceb7..00000000000000 --- a/x-pack/plugins/security/public/account_management/account_management_page.tsx +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React, { useEffect, useState } from 'react'; -import ReactDOM from 'react-dom'; -import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; -import type { PublicMethodsOf } from '@kbn/utility-types'; -import { CoreStart, NotificationsStart } from 'src/core/public'; -import { getUserDisplayName, AuthenticatedUser } from '../../common/model'; -import { AuthenticationServiceSetup } from '../authentication'; -import { UserAPIClient } from '../management'; -import { ChangePassword } from './change_password'; -import { PersonalInfo } from './personal_info'; - -interface Props { - authc: AuthenticationServiceSetup; - userAPIClient: PublicMethodsOf; - notifications: NotificationsStart; -} - -export const AccountManagementPage = ({ userAPIClient, authc, notifications }: Props) => { - const [currentUser, setCurrentUser] = useState(null); - useEffect(() => { - authc.getCurrentUser().then(setCurrentUser); - }, [authc]); - - if (!currentUser) { - return null; - } - - return ( - - - - -

{getUserDisplayName(currentUser)}

-
- - - - - - -
-
-
- ); -}; - -export function renderAccountManagementPage( - i18nStart: CoreStart['i18n'], - element: Element, - props: Props -) { - ReactDOM.render( - - - , - element - ); - - return () => ReactDOM.unmountComponentAtNode(element); -} diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx new file mode 100644 index 00000000000000..085121cf9e127b --- /dev/null +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { EuiLink, EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +export interface ApiKeysEmptyPromptProps { + error?: Error; +} + +export const ApiKeysEmptyPrompt: FunctionComponent = ({ + error, + children, +}) => { + const { services } = useKibana(); + + if (error) { + const { statusCode, message = '' } = (error as any).body ?? {}; + + if (statusCode === 400 && message.indexOf('[feature_not_enabled_exception]') !== -1) { + return ( + +

+ +

+

+ + + +

+ + } + /> + ); + } + + if (statusCode === 403) { + return ( + + +

+ } + /> + ); + } + + return ( + + +

+ } + actions={children} + /> + ); + } + + return ( + + + + } + body={ +

+ +

+ } + actions={children} + /> + ); +}; diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx new file mode 100644 index 00000000000000..5a364a2c3a353d --- /dev/null +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent, useState, useEffect } from 'react'; +import { Route, useHistory } from 'react-router-dom'; +import { useAsyncFn } from 'react-use'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { reactRouterNavigate, useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { SectionLoading } from '../../../../../../src/plugins/es_ui_shared/public'; +import { ApiKey } from '../../../common/model'; +import { + APIKeysAPIClient, + CreateApiKeyResponse, +} from '../../management/api_keys/api_keys_api_client'; +import { FieldTextWithCopyButton } from '../components/field_text_with_copy_button'; +import { Breadcrumb } from '../components/breadcrumb'; +import { CreateApiKeyFlyout } from './create_api_key_flyout'; +import { InvalidateApiKeyModal } from './invalidate_api_key_modal'; +import { ApiKeysTable } from './api_keys_table'; +import { ApiKeysEmptyPrompt } from './api_keys_empty_prompt'; + +export const ApiKeysPage: FunctionComponent = () => { + const history = useHistory(); + const { services } = useKibana(); + const apiKeysApiClient = new APIKeysAPIClient(services.http!); + const [state, getApiKeys] = useAsyncFn(apiKeysApiClient.getApiKeys, [services.http]); + const [createdApiKey, setCreatedApiKey] = useState(); + const [apiKeyToInvalidate, setApiKeyToInvalidate] = useState(); + + useEffect(() => { + getApiKeys(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + if (!state.value) { + if (state.error && !state.loading) { + return ( + + getApiKeys()}> + + + + ); + } + return ( + + + + ); + } + + return ( + <> + + + history.push({ pathname: '/api-keys' })} + onSuccess={(apiKey) => { + history.push({ pathname: '/api-keys' }); + setCreatedApiKey(apiKey); + getApiKeys(); + }} + /> + + + + {apiKeyToInvalidate && ( + setApiKeyToInvalidate(undefined)} + onSuccess={() => { + setApiKeyToInvalidate(undefined); + setCreatedApiKey(undefined); + getApiKeys(); + }} + /> + )} + + {!state.loading && state.value.apiKeys.length === 0 ? ( + + + + + + ) : ( + <> + + + + + + + + + + + + {createdApiKey && !state.loading && ( + <> + + +

+ +

+ +
+ + )} + + + + + )} + + ); +}; diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx new file mode 100644 index 00000000000000..cff43273800e2d --- /dev/null +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx @@ -0,0 +1,139 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +// @ts-ignore +import { formatDate } from '@elastic/eui/lib/services/format'; +import { + EuiBadge, + EuiButtonEmpty, + EuiHideFor, + EuiInMemoryTable, + EuiInMemoryTableProps, + EuiShowFor, + EuiText, + EuiButtonIcon, +} from '@elastic/eui'; +import { ApiKey } from '../../../common/model'; + +export type ApiKeysTableProps = Omit, 'columns'> & { + createdItemId?: ApiKey['id']; + onInvalidateItem(item: ApiKey): void; +}; + +export const ApiKeysTable: FunctionComponent = ({ + createdItemId, + onInvalidateItem, + ...props +}) => { + const actions = [ + { + render: (item: ApiKey) => ( + <> + + { + onInvalidateItem(item); + }} + /> + + + { + onInvalidateItem(item); + }} + > + + + + + ), + }, + ]; + + return ( + + !expiration ? ( + + + + ) : ( + formatDate(expiration) + ), + width: '25%', + mobileOptions: { show: false }, + }, + { + field: 'creation', + name: i18n.translate('xpack.security.accountManagement.apiKeys.createdHeader', { + defaultMessage: 'Created', + }), + render: (creation: number, item: ApiKey) => + item.id === createdItemId ? ( + + + + ) : ( + formatDate(creation) + ), + width: '25%', + }, + { + actions, + width: '25%', + }, + ]} + sorting={{ + sort: { + field: 'creation', + direction: 'desc', + }, + }} + pagination={true} + /> + ); +}; diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx new file mode 100644 index 00000000000000..faafc6809e349c --- /dev/null +++ b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx @@ -0,0 +1,273 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useRef, FunctionComponent } from 'react'; +import { + EuiCallOut, + EuiCodeEditor, + EuiFieldNumber, + EuiFieldText, + EuiForm, + EuiFormRow, + EuiSpacer, + EuiSwitch, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + APIKeysAPIClient, + CreateApiKeyRequest, + CreateApiKeyResponse, +} from '../../management/api_keys/api_keys_api_client'; +import { useForm, ValidationErrors } from '../components/use_form'; +import { FormFlyout, FormFlyoutProps } from '../components/form_flyout'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +export interface FormValues { + name: string; + expiration: string; + customExpiration: boolean; + customPriviliges: boolean; + role_descriptors: string; +} + +export interface CreateApiKeyFlyoutProps { + defaultValues?: Partial; + onSuccess?: (apiKey: CreateApiKeyResponse) => void; + onError?: (error: Error) => void; + onClose: FormFlyoutProps['onClose']; +} + +export const CreateApiKeyFlyout: FunctionComponent = ({ + onSuccess, + onError, + onClose, + defaultValues, +}) => { + const { services } = useKibana(); + const [form, eventHandlers] = useForm( + { + onSubmit: (values) => new APIKeysAPIClient(services.http!).createApiKey(mapValues(values)), + onSubmitSuccess: onSuccess, + onSubmitError: onError, + validate, + defaultValues, + }, + [services.http] + ); + const nameInput = useRef(null); + + return ( + + {form.submitError && ( + <> + + {(form.submitError as any).body?.message || form.submitError.message} + + + + )} + + + + + + + form.setValue('customPriviliges', e.target.checked)} + /> + {form.values.customPriviliges && ( + <> + + + form.setValue('role_descriptors', value)} + onBlur={() => form.trigger('role_descriptors')} + value={form.values.role_descriptors} + height="300px" + mode="json" + /> + + + )} + + + form.setValue('customExpiration', e.target.checked)} + /> + {form.values.customExpiration && ( + <> + + + + + + )} + + + ); +}; + +CreateApiKeyFlyout.defaultProps = { + defaultValues: { + customExpiration: false, + customPriviliges: false, + role_descriptors: JSON.stringify( + { + 'role-a': { + cluster: ['all'], + index: [ + { + names: ['index-a*'], + privileges: ['read'], + }, + ], + }, + 'role-b': { + cluster: ['all'], + index: [ + { + names: ['index-b*'], + privileges: ['all'], + }, + ], + }, + }, + null, + 2 + ), + }, +}; + +export function validate(v: Partial) { + const errors: ValidationErrors = {}; + + if (!v.name) { + errors.name = 'Enter a name.'; + } + + if (v.customExpiration && !v.expiration) { + errors.expiration = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.successNotification', + { + defaultMessage: 'Enter a duration or disable the option.', + } + ); + } + + if (v.customPriviliges) { + if (!v.role_descriptors) { + errors.role_descriptors = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.successNotification', + { + defaultMessage: 'Enter role descriptors or disable the option.', + } + ); + } else { + try { + JSON.parse(v.role_descriptors); + } catch (e) { + errors.role_descriptors = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.successNotification', + { + defaultMessage: 'Enter valid JSON.', + } + ); + } + } + } + + return errors; +} + +export function mapValues(values: FormValues): CreateApiKeyRequest { + return { + name: values.name, + expiration: values.customExpiration && values.expiration ? `${values.expiration}d` : undefined, + role_descriptors: + values.customPriviliges && values.role_descriptors + ? JSON.parse(values.role_descriptors) + : undefined, + }; +} diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/index.ts b/x-pack/plugins/security/public/account_management/api_keys_page/index.ts new file mode 100644 index 00000000000000..d4821de33b201d --- /dev/null +++ b/x-pack/plugins/security/public/account_management/api_keys_page/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ApiKeysEmptyPrompt } from './api_keys_empty_prompt'; +export { ApiKeysPage as default } from './api_keys_page'; // eslint-disable-line import/no-default-export +export { ApiKeysTable } from './api_keys_table'; +export { CreateApiKeyFlyout } from './create_api_key_flyout'; +export { InvalidateApiKeyModal } from './invalidate_api_key_modal'; diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx new file mode 100644 index 00000000000000..11c16cea33b3b8 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiText } from '@elastic/eui'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { ApiKeyToInvalidate } from '../../../common/model'; +import { + APIKeysAPIClient, + InvalidateApiKeysResponse, +} from '../../management/api_keys/api_keys_api_client'; +import { useSubmitHandler } from '../components/use_form'; +import { ConfirmModal, ConfirmModalProps } from '../components/confirm_modal'; + +export interface InvalidateApiKeyModalProps { + onCancel: ConfirmModalProps['onCancel']; + onSuccess?(result: InvalidateApiKeysResponse): any; + onError?(error: Error): any; + apiKey: ApiKeyToInvalidate; +} + +export const InvalidateApiKeyModal: FunctionComponent = ({ + onCancel, + onSuccess, + onError, + apiKey, +}) => { + const { services, notifications } = useKibana(); + const [state, invalidateApiKeys] = useSubmitHandler( + { + onSubmit: () => + new APIKeysAPIClient(services.http!).invalidateApiKeys([ + { id: apiKey.id, name: apiKey.name }, + ]), + onSubmitSuccess: (values) => { + notifications.toasts.success({ + iconType: 'check', + title: i18n.translate( + 'xpack.security.accountManagement.invalidateApiKey.successMessage', + { + defaultMessage: 'Invalidated API key “{name}”', + values: { name: apiKey.name }, + } + ), + toastLifeTimeMs: 3000, + }); + onSuccess?.(values); + }, + onSubmitError: (error) => { + notifications.toasts.danger({ + iconType: 'alert', + title: i18n.translate('xpack.security.accountManagement.invalidateApiKey.errorMessage', { + defaultMessage: 'Could not invalidate API key “{name}”', + values: { name: apiKey.name }, + }), + body: (error as any).body?.message || error.message, + toastLifeTimeMs: 1500, + }); + onError?.(error); + }, + }, + [services.http] + ); + + return ( + + +

+ +

+
+
+ ); +}; diff --git a/x-pack/plugins/security/public/account_management/components/breadcrumb.tsx b/x-pack/plugins/security/public/account_management/components/breadcrumb.tsx new file mode 100644 index 00000000000000..52cd721b658c35 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/components/breadcrumb.tsx @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { createContext, useEffect, useRef, FunctionComponent, ReactNode } from 'react'; +import { EuiBreadcrumb } from '@elastic/eui'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +/** + * Component that sets breadcrumbs and doc title based on the render tree. + * + * @example + * ```typescript + * + * + * {showForm && ( + * + *
+ *
+ * )} + * + * ``` + */ +export const Breadcrumb: FunctionComponent = ({ children, ...breadcrumb }) => ( + + {(value) => + value ? ( + + {children} + + ) : ( + {children} + ) + } + +); + +interface BreadcrumbContext { + parents: BreadcrumbProps[]; + onMount(breadcrumbs: BreadcrumbProps[]): void; + onUnmount(breadcrumbs: BreadcrumbProps[]): void; +} + +const { Provider, Consumer } = createContext(undefined); + +export interface RootProviderProps { + breadcrumb: BreadcrumbProps; + children: ReactNode; +} + +export const RootProvider: FunctionComponent = ({ breadcrumb, children }) => { + const { services } = useKibana(); + const breadcrumbsRef = useRef([]); + + const setBreadcrumbs = (breadcrumbs: BreadcrumbProps[]) => { + breadcrumbsRef.current = breadcrumbs; + services.chrome?.setBreadcrumbs(breadcrumbs); + services.chrome?.docTitle.change(getDocTitle(breadcrumbs)); + }; + + return ( + { + if (breadcrumbs.length > breadcrumbsRef.current.length) { + setBreadcrumbs(breadcrumbs); + } + }} + onUnmount={(breadcrumbs) => { + if (breadcrumbs.length < breadcrumbsRef.current.length) { + setBreadcrumbs(breadcrumbs); + } + }} + > + {children} + + ); +}; + +export function getDocTitle(breadcrumbs: BreadcrumbProps[]) { + return breadcrumbs + .slice() + .reverse() + .map(({ text }) => text); +} + +export const NestedProvider: FunctionComponent = ({ + parents, + onMount, + onUnmount, + breadcrumb, + children, +}) => { + const nextParents = [...parents, breadcrumb]; + + useEffect(() => { + onMount(nextParents); + return () => onUnmount(parents); + }, [breadcrumb.text, breadcrumb.href]); // eslint-disable-line react-hooks/exhaustive-deps + + return {children}; +}; + +export interface BreadcrumbProps extends EuiBreadcrumb { + text: string; +} diff --git a/x-pack/plugins/security/public/account_management/components/confirm_modal.tsx b/x-pack/plugins/security/public/account_management/components/confirm_modal.tsx new file mode 100644 index 00000000000000..ece2dd000490ea --- /dev/null +++ b/x-pack/plugins/security/public/account_management/components/confirm_modal.tsx @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { + EuiButton, + EuiButtonProps, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiModal, + EuiModalBody, + EuiModalFooter, + EuiModalHeader, + EuiModalHeaderTitle, + EuiModalProps, + EuiOverlayMask, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +export interface ConfirmModalProps extends Omit { + confirmButtonColor?: EuiButtonProps['color']; + confirmButtonText: string; + isLoading?: EuiButtonProps['isLoading']; + onCancel(): void; + onConfirm(): void; + ownFocus?: boolean; +} + +export const ConfirmModal: FunctionComponent = ({ + children, + confirmButtonColor, + confirmButtonText, + isLoading, + onCancel, + onConfirm, + ownFocus, + title, + ...rest +}) => { + const modal = ( + + + {title} + + {children} + + + + + + + + + + {confirmButtonText} + + + + + + ); + + return ownFocus ? {modal} : modal; +}; + +ConfirmModal.defaultProps = { + ownFocus: true, +}; diff --git a/x-pack/plugins/security/public/account_management/components/field_text_with_copy_button.tsx b/x-pack/plugins/security/public/account_management/components/field_text_with_copy_button.tsx new file mode 100644 index 00000000000000..5c32b74f5af6bf --- /dev/null +++ b/x-pack/plugins/security/public/account_management/components/field_text_with_copy_button.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { EuiButtonIcon, EuiCopy, EuiFieldText, EuiFieldTextProps } from '@elastic/eui'; + +export interface FieldTextWithCopyButtonProps extends Omit { + value: string; +} + +export const FieldTextWithCopyButton: FunctionComponent = (props) => ( + + {(copyText) => ( + + )} + + } + /> +); + +FieldTextWithCopyButton.defaultProps = { + readOnly: true, +}; diff --git a/x-pack/plugins/security/public/account_management/components/form_flyout.tsx b/x-pack/plugins/security/public/account_management/components/form_flyout.tsx new file mode 100644 index 00000000000000..92d282bc972ccf --- /dev/null +++ b/x-pack/plugins/security/public/account_management/components/form_flyout.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, FunctionComponent, MouseEventHandler, RefObject } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiTitle, + EuiFlyout, + EuiFlyoutProps, + EuiFlyoutHeader, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonProps, + EuiButtonEmpty, + EuiPortal, +} from '@elastic/eui'; + +export interface FormFlyoutProps extends Omit { + title: string; + isLoading?: EuiButtonProps['isLoading']; + initialFocus?: RefObject; + onSubmit: MouseEventHandler; + submitButtonText: string; + submitButtonColor?: EuiButtonProps['color']; +} + +export const FormFlyout: FunctionComponent = ({ + title, + submitButtonText, + submitButtonColor, + onSubmit, + isLoading, + children, + initialFocus, + ...rest +}) => { + useEffect(() => { + if (initialFocus && initialFocus.current) { + initialFocus.current.focus(); + } + }, [initialFocus]); + + const flyout = ( + + + +

{title}

+
+
+ {children} + + + + + + + + + + {submitButtonText} + + + + +
+ ); + + return rest.ownFocus ? {flyout} : flyout; +}; + +FormFlyout.defaultProps = { + ownFocus: true, +}; diff --git a/x-pack/plugins/security/public/account_management/components/tabbed_routes.tsx b/x-pack/plugins/security/public/account_management/components/tabbed_routes.tsx new file mode 100644 index 00000000000000..0acbbd08ba68eb --- /dev/null +++ b/x-pack/plugins/security/public/account_management/components/tabbed_routes.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { EuiTabs, EuiTab, EuiTabbedContentTab, EuiSpacer } from '@elastic/eui'; +import { Switch, Route, RouteProps, RedirectProps } from 'react-router-dom'; +import { reactRouterNavigate } from '../../../../../../src/plugins/kibana_react/public'; + +export type TabbedRoutesTab = EuiTabbedContentTab & + Pick & + Pick; + +export interface TabbedRoutesProps { + routes: TabbedRoutesTab[]; +} +export const TabbedRoutes: FunctionComponent = ({ routes }) => { + return ( + <> + + {routes.map((route) => ( + + {({ match, history }) => ( + + {route.name} + + )} + + ))} + + + + {routes.map((route) => ( + + {route.content} + + ))} + + + ); +}; diff --git a/x-pack/plugins/security/public/account_management/components/use_form.ts b/x-pack/plugins/security/public/account_management/components/use_form.ts new file mode 100644 index 00000000000000..80021b3a03d16f --- /dev/null +++ b/x-pack/plugins/security/public/account_management/components/use_form.ts @@ -0,0 +1,235 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + useState, + useEffect, + DependencyList, + FocusEventHandler, + ChangeEventHandler, + ReactEventHandler, +} from 'react'; +import { useAsyncFn } from 'react-use'; + +export interface FormOptions extends SubmitHandlerOptions { + validate: ValidateFunction; + defaultValues?: Partial; +} + +export interface FormProps { + onSubmit: ReactEventHandler; + onChange: ChangeEventHandler; + onBlur: FocusEventHandler; + noValidate: boolean; +} + +export type FormReturnTuple = [ + ValidationState & SubmitState, + FormProps +]; + +/** + * Returns state and {@link HTMLFormElement} event handlers useful for creating + * forms with inline validation. + * + * @see {@link useInlineValidation} if you don't want to use {@link HTMLFormElement}. + * @see {@link useSubmitHandler} if you don't require inline validation. + * + * @example + * ```typescript + * const [form, eventHandlers] = useForm({ + * onSubmit: (values) => apiClient.create(values), + * validate: (values) => !values.email ? { email: 'Required' } : {} + * }); + * + * + * + * Submit + * + * ``` + */ +export function useForm( + options: FormOptions, + deps?: DependencyList +): FormReturnTuple { + const [submitState, submit] = useSubmitHandler(options, deps); + const validationState = useInlineValidation(options.validate, options.defaultValues); + + const eventHandlers: FormProps = { + onSubmit: (event) => { + event.preventDefault(); + validationState.handleSubmit(submit); + }, + onChange: (event) => { + const { name, type, checked, value } = event.target; + validationState.setValue(name, type === 'checkbox' ? checked : value); + }, + onBlur: (event) => { + validationState.trigger(event.target.name); + }, + noValidate: true, // Native browser validation gets in the way of EUI + }; + + return [{ ...validationState, ...submitState }, eventHandlers]; +} + +export type ValidateFunction = (values: Partial) => ValidationErrors; +export type ValidationErrors = Partial>; +export type DirtyFields = Partial>; +export type SubmitCallback = (values: Values) => Promise; + +export interface ValidationState { + setValue(name: string, value: any): void; + setError(name: string, message: string): void; + trigger(name: string): void; + handleSubmit(onSubmit: SubmitCallback): Promise; + values: Partial; + errors: ValidationErrors; + isInvalid: boolean; + isSubmitted: boolean; +} + +/** + * Returns state useful for creating forms with inline validation. + * + * @example + * ```typescript + * const form = useInlineValidation((values) => !values.toggle ? { toggle: 'Required' } : {}); + * + * form.setValue('toggle', e.target.checked)} + * onBlur={() => form.trigger('toggle')} + * isInvalid={!!form.errors.toggle} + * /> + * apiClient.create(values))}> + * Submit + * + * ``` + */ +export function useInlineValidation( + validate: ValidateFunction, + defaultValues: Partial = {} +): ValidationState { + const [values, setValues] = useState>(defaultValues); + const [errors, setErrors] = useState>({}); + const [dirty, setDirty] = useState>({}); + const [submitCount, setSubmitCount] = useState(0); + + const isSubmitted = submitCount > 0; + const isInvalid = Object.keys(errors).length > 0; + + const inlineValidation = (nextValues: Partial, nextDirty: DirtyFields) => { + const nextErrors = getIntersection(validate(nextValues), nextDirty); + setErrors(nextErrors); + if (Object.keys(nextErrors).length === 0) { + setSubmitCount(0); + } + }; + + return { + setValue: (name, value) => { + const nextValues = { ...values, [name]: value }; + setValues(nextValues); + inlineValidation(nextValues, dirty); + }, + setError: (name, message) => { + setErrors({ ...errors, [name]: message }); + setDirty({ ...dirty, [name]: true }); + }, + trigger: (name) => { + const nextDirty = { ...dirty, [name]: true }; + setDirty(nextDirty); + inlineValidation(values, nextDirty); + }, + handleSubmit: async (onSubmit) => { + const nextErrors = validate(values); + setDirty({ ...dirty, ...nextErrors }); + setErrors(nextErrors); + setSubmitCount(submitCount + 1); + if (Object.keys(nextErrors).length === 0) { + return await onSubmit(values as Values); + } + }, + values, + errors, + isInvalid, + isSubmitted, + }; +} + +export function getIntersection( + errors: ValidationErrors, + dirty: DirtyFields +) { + const names = Object.keys(errors) as Array; + return names.reduce>((acc, name) => { + if (dirty[name]) { + acc[name] = errors[name]; + } + return acc; + }, {}); +} + +export type AsyncFunction = (...args: any[]) => Promise; + +export interface SubmitHandlerOptions { + onSubmit: AsyncFunction; + onSubmitSuccess?: (result: Result) => void; + onSubmitError?: (error: Error) => void; +} + +export interface SubmitState { + isSubmitting: boolean; + submitError?: Error; + submitResult?: Result; +} + +export type SubmitHandlerReturnTuple = [SubmitState, AsyncFunction]; + +/** + * Tracks state of an async function and triggers callbacks with the outcome. + * + * @example + * ```typescript + * const [state, deleteUser] = useSubmitHandler({ + * onSubmit: () => apiClient.deleteUser(), + * onSubmitSuccess: (result) => toasts.addSuccess('Deleted user'), + * onSubmitError: (error) => toasts.addError('Could not delete user'), + * }); + * + * + * Delete user + * + * ``` + */ +export function useSubmitHandler( + { onSubmit, onSubmitSuccess, onSubmitError }: SubmitHandlerOptions, + deps?: DependencyList +): SubmitHandlerReturnTuple { + const [state, callback] = useAsyncFn(onSubmit, deps); + + useEffect(() => { + if (state.value && onSubmitSuccess) { + onSubmitSuccess(state.value); + } + }, [state.value]); // eslint-disable-line react-hooks/exhaustive-deps + + useEffect(() => { + if (state.error && onSubmitError) { + onSubmitError(state.error); + } + }, [state.error]); // eslint-disable-line react-hooks/exhaustive-deps + + return [ + { + isSubmitting: state.loading, + submitError: state.loading ? undefined : state.error, + submitResult: state.value, + }, + callback, + ]; +} diff --git a/x-pack/plugins/security/public/account_management/personal_info/index.ts b/x-pack/plugins/security/public/account_management/personal_info/index.ts deleted file mode 100644 index 5980157f5b76e2..00000000000000 --- a/x-pack/plugins/security/public/account_management/personal_info/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { PersonalInfo } from './personal_info'; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts index a127379d972413..ac8655a7e49a50 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts @@ -5,39 +5,60 @@ */ import { HttpStart } from 'src/core/public'; -import { ApiKey, ApiKeyToInvalidate } from '../../../common/model'; +import { ApiKey, ApiKeyToInvalidate, Role } from '../../../common/model'; -interface CheckPrivilegesResponse { +export interface CheckPrivilegesResponse { areApiKeysEnabled: boolean; isAdmin: boolean; canManage: boolean; } -interface InvalidateApiKeysResponse { +export interface InvalidateApiKeysResponse { itemsInvalidated: ApiKeyToInvalidate[]; errors: any[]; } -interface GetApiKeysResponse { +export interface GetApiKeysResponse { apiKeys: ApiKey[]; } +export interface CreateApiKeyRequest { + name: string; + expiration?: string; + role_descriptors?: { + [key in string]: Role['elasticsearch']; + }; +} + +export interface CreateApiKeyResponse { + id: string; + name: string; + expiration: number; + api_key: string; +} + const apiKeysUrl = '/internal/security/api_key'; export class APIKeysAPIClient { constructor(private readonly http: HttpStart) {} - public async checkPrivileges() { + public checkPrivileges = async () => { return await this.http.get(`${apiKeysUrl}/privileges`); - } + }; - public async getApiKeys(isAdmin = false) { + public getApiKeys = async (isAdmin = false) => { return await this.http.get(apiKeysUrl, { query: { isAdmin } }); - } + }; - public async invalidateApiKeys(apiKeys: ApiKeyToInvalidate[], isAdmin = false) { + public invalidateApiKeys = async (apiKeys: ApiKeyToInvalidate[], isAdmin = false) => { return await this.http.post(`${apiKeysUrl}/invalidate`, { body: JSON.stringify({ apiKeys, isAdmin }), }); - } + }; + + public createApiKey = async (apiKey: CreateApiKeyRequest) => { + return await this.http.post(apiKeysUrl, { + body: JSON.stringify(apiKey), + }); + }; } diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts new file mode 100644 index 00000000000000..dfef44e9b6d92f --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/create.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { ApiKey } from '../../../common/model'; +import { wrapError, wrapIntoCustomErrorResponse } from '../../errors'; +import { RouteDefinitionParams } from '..'; + +interface ResponseType { + itemsInvalidated: Array>; + errors: Array & { error: Error }>; +} + +export function defineCreateApiKeyRoutes({ router, clusterClient }: RouteDefinitionParams) { + router.post( + { + path: '/internal/security/api_key', + validate: { + body: schema.object({ + name: schema.string(), + expiration: schema.maybe(schema.string()), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const scopedClusterClient = clusterClient.asScoped(request); + try { + const apiKey = await scopedClusterClient.callAsCurrentUser('shield.createAPIKey', { + body: request.body, + }); + + return response.ok({ body: apiKey }); + } catch (error) { + return response.customError(wrapIntoCustomErrorResponse(error)); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/api_keys/index.ts b/x-pack/plugins/security/server/routes/api_keys/index.ts index 7ac37bbead6136..aa148732e78708 100644 --- a/x-pack/plugins/security/server/routes/api_keys/index.ts +++ b/x-pack/plugins/security/server/routes/api_keys/index.ts @@ -8,6 +8,7 @@ import { defineGetApiKeysRoutes } from './get'; import { defineCheckPrivilegesRoutes } from './privileges'; import { defineInvalidateApiKeysRoutes } from './invalidate'; import { defineEnabledApiKeysRoutes } from './enabled'; +import { defineCreateApiKeyRoutes } from './create'; import { RouteDefinitionParams } from '..'; export function defineApiKeysRoutes(params: RouteDefinitionParams) { @@ -15,4 +16,5 @@ export function defineApiKeysRoutes(params: RouteDefinitionParams) { defineGetApiKeysRoutes(params); defineCheckPrivilegesRoutes(params); defineInvalidateApiKeysRoutes(params); + defineCreateApiKeyRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/views/account_management.ts b/x-pack/plugins/security/server/routes/views/account_management.ts index 696a5e12b64c13..2ccca46ea3418f 100644 --- a/x-pack/plugins/security/server/routes/views/account_management.ts +++ b/x-pack/plugins/security/server/routes/views/account_management.ts @@ -13,4 +13,12 @@ export function defineAccountManagementRoutes({ httpResources }: RouteDefinition httpResources.register({ path: '/security/account', validate: false }, (context, req, res) => res.renderCoreApp() ); + httpResources.register( + { path: '/security/account/api-keys', validate: false }, + (context, req, res) => res.renderCoreApp() + ); + httpResources.register( + { path: '/security/account/api-keys/create', validate: false }, + (context, req, res) => res.renderCoreApp() + ); } diff --git a/yarn.lock b/yarn.lock index 8de8e0a8c0eb20..bd64b8cdd07e14 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1074,13 +1074,20 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.3", "@babel/runtime@^7.11.2", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.4.5", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": version "7.11.2" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.11.2.tgz#f549c13c754cc40b87644b9fa9f09a6a95fe0736" integrity sha512-TeWkU52so0mPtDcaCTxNBI/IHiz0pZgr8VEFqXFtZWpYD08ZB6FaSwVAS8MKRQAP3bYKiVjwysOJgMFY28o6Tw== dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.1.2": + version "7.12.1" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.12.1.tgz#b4116a6b6711d010b2dad3b7b6e43bf1b9954740" + integrity sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.10.4", "@babel/template@^7.3.3": version "7.10.4" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.10.4.tgz#3251996c4200ebc71d1a8fc405fba940f36ba278" @@ -4241,10 +4248,10 @@ dependencies: "@types/sizzle" "*" -"@types/js-cookie@2.2.5": - version "2.2.5" - resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.5.tgz#38dfaacae8623b37cc0b0d27398e574e3fc28b1e" - integrity sha512-cpmwBRcHJmmZx0OGU7aPVwGWGbs4iKwVYchk9iuMtxNCA2zorwdaTz4GkLgs2WGxiRZRFKnV1k6tRUHX7tBMxg== +"@types/js-cookie@2.2.6": + version "2.2.6" + resolved "https://registry.yarnpkg.com/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" + integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== "@types/js-search@^1.4.0": version "1.4.0" @@ -5492,10 +5499,10 @@ dependencies: tslib "^1.9.3" -"@xobotyi/scrollbar-width@1.9.4": - version "1.9.4" - resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.4.tgz#a7dce20b7465bcad29cd6bbb557695e4ea7863cb" - integrity sha512-o12FCQt/X5n3pgKEWGpt0f/7Eg4mfv3uRwPUrctiOT8ZuxbH3cNLGWfH/8y6KxVJg4L2885ucuXQ6XECZzUiJA== +"@xobotyi/scrollbar-width@1.9.5": + version "1.9.5" + resolved "https://registry.yarnpkg.com/@xobotyi/scrollbar-width/-/scrollbar-width-1.9.5.tgz#80224a6919272f405b87913ca13b92929bdf3c4d" + integrity sha512-N8tkAACJx2ww8vFMneJmaAgmjAG1tnVBZJRLRcx061tmsLRZHSEZSLuGWnwPtunsSLvSqXQ2wfp7Mgqg1I+2dQ== "@xtuc/ieee754@^1.2.0": version "1.2.0" @@ -5754,7 +5761,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@^4.7.0: +ajv@^4.7.0, ajv@^4.9.1: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= @@ -5762,6 +5769,16 @@ ajv@^4.7.0: co "^4.6.0" json-stable-stringify "^1.0.1" +ajv@^5.0.0: + version "5.5.2" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= + dependencies: + co "^4.6.0" + fast-deep-equal "^1.0.0" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.3.0" + ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.5.5, ajv@^6.9.1: version "6.12.4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234" @@ -5772,6 +5789,16 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.5.5, ajv@ json-schema-traverse "^0.4.1" uri-js "^4.2.2" +ajv@^6.12.3: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + dependencies: + fast-deep-equal "^3.1.1" + fast-json-stable-stringify "^2.0.0" + json-schema-traverse "^0.4.1" + uri-js "^4.2.2" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -6560,6 +6587,11 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= + assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" @@ -6763,11 +6795,21 @@ await-event@^2.1.0: resolved "https://registry.yarnpkg.com/await-event/-/await-event-2.1.0.tgz#78e9f92684bae4022f9fa0b5f314a11550f9aa76" integrity sha1-eOn5JoS65AIvn6C18xShFVD5qnY= +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= + aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= +aws4@^1.2.1: + version "1.10.1" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" + integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== + aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" @@ -7557,6 +7599,13 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= + dependencies: + hoek "2.x.x" + boom@7.x.x, boom@^7.1.0, boom@^7.2.0: version "7.2.2" resolved "https://registry.yarnpkg.com/boom/-/boom-7.2.2.tgz#ac92101451aa5cea901aed07d881dd32b4f08345" @@ -8973,7 +9022,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: +combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.5, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -9308,13 +9357,20 @@ copy-props@^2.0.1: each-props "^1.3.0" is-plain-object "^2.0.1" -copy-to-clipboard@^3.0.8, copy-to-clipboard@^3.2.0: +copy-to-clipboard@^3.0.8: version "3.2.0" resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.2.0.tgz#d2724a3ccbfed89706fac8a894872c979ac74467" integrity sha512-eOZERzvCmxS8HWzugj4Uxl8OJxa7T2k1Gi0X5qavwydHIfuSHq2dTD09LOg/XyGq4Zpb5IsR/2OJ5lbOegz78w== dependencies: toggle-selection "^1.0.6" +copy-to-clipboard@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/copy-to-clipboard/-/copy-to-clipboard-3.3.1.tgz#115aa1a9998ffab6196f93076ad6da3b913662ae" + integrity sha512-i13qo6kIHTTpCm8/Wup+0b1mVWETvu2kIMzKoK8FpkLkFxlt0znUAHcMzox+T8sPlqtZXq3CulEjQHsYiGFJUw== + dependencies: + toggle-selection "^1.0.6" + copy-webpack-plugin@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/copy-webpack-plugin/-/copy-webpack-plugin-6.0.2.tgz#10efc6ad219a61acbf2f5fb50af83da38431bc34" @@ -9571,6 +9627,13 @@ crypt@~0.0.1: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= + dependencies: + boom "2.x.x" + cryptiles@4.x.x: version "4.1.3" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.3.tgz#2461d3390ea0b82c643a6ba79f0ed491b0934c25" @@ -9702,7 +9765,7 @@ css-to-react-native@^3.0.0: css-color-keywords "^1.0.0" postcss-value-parser "^4.0.2" -css-tree@1.0.0-alpha.37, css-tree@^1.0.0-alpha.28: +css-tree@1.0.0-alpha.37: version "1.0.0-alpha.37" resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.37.tgz#98bebd62c4c1d9f960ec340cf9f7522e30709a22" integrity sha512-DMxWJg0rnz7UgxKT0Q1HU/L9BeJI0M6ksor0OgqOnF+aRCDWg/N2641HmVyU9KVIu0OVVWOb2IpC9A+BJRnejg== @@ -9710,6 +9773,14 @@ css-tree@1.0.0-alpha.37, css-tree@^1.0.0-alpha.28: mdn-data "2.0.4" source-map "^0.6.1" +css-tree@^1.0.0-alpha.28: + version "1.0.0-alpha.39" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.0.0-alpha.39.tgz#2bff3ffe1bb3f776cf7eefd91ee5cba77a149eeb" + integrity sha512-7UvkEYgBAHRG9Nt980lYxjsTrCyHFN53ky3wVsDkiMdVqylqRt+Zc+jm5qw7/qyOvN2dHSYtX0e4MbCCExSvnA== + dependencies: + mdn-data "2.0.6" + source-map "^0.6.1" + css-what@2.1, css-what@^2.1.2: version "2.1.3" resolved "https://registry.yarnpkg.com/css-what/-/css-what-2.1.3.tgz#a6d7604573365fe74686c3f311c56513d88285f2" @@ -9785,11 +9856,16 @@ cssstyle@^2.2.0: dependencies: cssom "~0.3.6" -csstype@^2.2.0, csstype@^2.5.5, csstype@^2.5.7, csstype@^2.6.7: +csstype@^2.2.0, csstype@^2.5.7, csstype@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.7.tgz#20b0024c20b6718f4eda3853a1f5a1cce7f5e4a5" integrity sha512-9Mcn9sFbGBAdmimWb2gLVDtFJzeKtDGIr76TUqmjZrw9LFXBMSU70lcs+C0/7fyCd6iBDqmksUcCOUIkisPHsQ== +csstype@^2.5.5: + version "2.6.13" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.13.tgz#a6893015b90e84dd6e85d0e3b442a1e84f2dbe0f" + integrity sha512-ul26pfSQTZW8dcOnD2iiJssfXw0gdNVX9IJDH/X3K5DGPfj+fUYe3kB+swUY6BF3oZDxaID3AJt+9/ojSAE05A== + currently-unhandled@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/currently-unhandled/-/currently-unhandled-0.4.1.tgz#988df33feab191ef799a61369dd76c17adf957ea" @@ -10389,7 +10465,12 @@ deep-object-diff@^1.1.0: resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== -deepmerge@3.2.0, deepmerge@^4.0.0, deepmerge@^4.2.2: +deepmerge@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.2.0.tgz#58ef463a57c08d376547f8869fdc5bcee957f44e" + integrity sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow== + +deepmerge@^4.0.0, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -11330,6 +11411,13 @@ encodeurl@^1.0.2, encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= +encoding@^0.1.11: + version "0.1.13" + resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" + integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== + dependencies: + iconv-lite "^0.6.2" + end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -11502,7 +11590,7 @@ error-stack-parser@^1.3.5: dependencies: stackframe "^0.3.1" -error-stack-parser@^2.0.4, error-stack-parser@^2.0.6: +error-stack-parser@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/error-stack-parser/-/error-stack-parser-2.0.6.tgz#5a99a707bd7a4c58a797902d48d82803ede6aad8" integrity sha512-d51brTeqC+BHlwF0BhPtcYgF5nlzf9ZZ0ZIUQNZpc9ZB9qw5IJ2diTrBY9jlCJkTLITYPjmiX6OWCwH+fuyNgQ== @@ -12390,7 +12478,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@~3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -12490,11 +12578,21 @@ fancy-log@^1.3.2: color-support "^1.1.3" time-stamp "^1.0.0" -fast-deep-equal@^3.1.1, fast-deep-equal@~3.1.3: +fast-deep-equal@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= + +fast-deep-equal@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== +fast-deep-equal@^3.1.3, fast-deep-equal@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + fast-diff@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" @@ -13169,6 +13267,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -14698,11 +14805,32 @@ hapi@^17.5.3: teamwork "3.x.x" topo "3.x.x" +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4= + har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio= + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +har-validator@~5.1.0: + version "5.1.5" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" + integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== + dependencies: + ajv "^6.12.3" + har-schema "^2.0.0" + har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" @@ -15004,6 +15132,16 @@ hat@0.0.3: resolved "https://registry.yarnpkg.com/hat/-/hat-0.0.3.tgz#bb014a9e64b3788aed8005917413d4ff3d502d8a" integrity sha1-uwFKnmSzeIrtgAWRdBPU/z1QLYo= +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + he@1.2.0, he@1.2.x, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -15072,6 +15210,11 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= + hoek@5.x.x, hoek@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da" @@ -15082,7 +15225,12 @@ hoek@6.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.0.3.tgz#7884360426d927865a0a1251fc9c59313af5b798" integrity sha512-TU6RyZ/XaQCTWRLrdqZZtZqwxUVr6PDMfi6MlWNURZ7A6czanQqX4pFE1mdOUQR9FdPCsZ0UzL8jI/izZ+eBSQ== -hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" + integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== + +hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -15337,6 +15485,15 @@ http-request-to-url@^1.0.0: await-event "^2.1.0" socket-location "^1.0.0" +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -15400,9 +15557,9 @@ hyperlinker@^1.0.0: integrity sha512-Ty8UblRWFEcfSuIaajM34LdPXIhbs1ajEX/BBPv24J+enSVaEVY63xQ6lTO9VRYS5LAoghIG0IDJ+p+IPzKUQQ== hyphenate-style-name@^1.0.2: - version "1.0.3" - resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.3.tgz#097bb7fa0b8f1a9cf0bd5c734cf95899981a9b48" - integrity sha512-EcuixamT82oplpoJ2XU4pDtKGWQ7b00CD9f1ug9IaQ3p1bkHMiKCZ9ut9QDI6qsa6cpUuB+A/I+zLtdNK4n2DQ== + version "1.0.4" + resolved "https://registry.yarnpkg.com/hyphenate-style-name/-/hyphenate-style-name-1.0.4.tgz#691879af8e220aea5750e8827db4ef62a54e361d" + integrity sha512-ygGZLjmXfPHj+ZWh6LwbC37l43MhfztxetbFCoYTM2VjkIUpeHgSNn7QIyVFj7YQ1Wl9Cbw5sholVJPzWvC2MQ== i18n-iso-countries@^4.3.1: version "4.3.1" @@ -15430,6 +15587,13 @@ iconv-lite@^0.5.0, iconv-lite@^0.5.1: dependencies: safer-buffer ">= 2.1.2 < 3" +iconv-lite@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" + integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== + dependencies: + safer-buffer ">= 2.1.2 < 3.0.0" + icss-utils@^4.0.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" @@ -16496,7 +16660,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -17576,6 +17740,11 @@ json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== +json-schema-traverse@^0.3.0: + version "0.3.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= + json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -18583,7 +18752,17 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +lodash@4.17.15: + version "4.17.15" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" + integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== + +lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -19039,6 +19218,11 @@ mdn-data@2.0.4: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.4.tgz#699b3c38ac6f1d728091a64650b65d388502fd5b" integrity sha512-iV3XNKw06j5Q7mi6h+9vbx23Tv7JkjEVgKHW4pimwyDGWm0OIQntJJ+u1C6mg6mK1EaTv42XQ7w76yuzH7M2cA== +mdn-data@2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.6.tgz#852dc60fcaa5daa2e8cf6c9189c440ed3e042978" + integrity sha512-rQvjv71olwNHgiTbfPZFkJtjNMciWgswYeciZhtvWLO8bmX3TnhyA62I6sTWOyZssWHJJjY6/KiWwqQsWWsqOA== + mdurl@^1.0.0, mdurl@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/mdurl/-/mdurl-1.0.1.tgz#fe85b2ec75a59037f2adfec100fd6c601761152e" @@ -19298,7 +19482,7 @@ mime-db@1.44.0, mime-db@1.x.x, "mime-db@>= 1.40.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: +mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.7: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -19399,7 +19583,22 @@ minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@0.0.8, minimist@1.1.x, minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@1.1.x: + version "1.1.3" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" + integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= + +minimist@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -19851,9 +20050,9 @@ nan@^2.12.1, nan@^2.13.2, nan@^2.14.0, nan@^2.14.1: integrity sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw== nano-css@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.2.1.tgz#73b8470fa40b028a134d3393ae36bbb34b9fa332" - integrity sha512-T54okxMAha0+de+W8o3qFtuWhTxYvqQh2ku1cYEqTTP9mR62nWV2lLK9qRuAGWmoaYWhU7K4evT9Lc1iF65wuw== + version "5.3.0" + resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.3.0.tgz#9d3cd29788d48b6a07f52aa4aec7cf4da427b6b5" + integrity sha512-uM/9NGK9/E9/sTpbIZ/bQ9xOLOIHZwrrb/CRlbDHBU/GFS7Gshl24v/WJhwsVViWkpOXUmiZ66XO7fSB4Wd92Q== dependencies: css-tree "^1.0.0-alpha.28" csstype "^2.5.5" @@ -20026,7 +20225,20 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@2.1.2, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" + integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= + +node-fetch@^1.0.1: + version "1.7.3" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" + integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== + dependencies: + encoding "^0.1.11" + is-stream "^1.0.1" + +node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -20036,11 +20248,16 @@ node-forge@0.9.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== -node-forge@^0.10.0, node-forge@^0.7.6: +node-forge@^0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== +node-forge@^0.7.6: + version "0.7.6" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" + integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== + node-gyp-build@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" @@ -20464,6 +20681,11 @@ nyc@^15.0.1: test-exclude "^6.0.0" yargs "^15.0.2" +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= + oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -22197,6 +22419,11 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= +psl@^1.1.24: + version "1.8.0" + resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" + integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== + psl@^1.1.28: version "1.4.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" @@ -22258,7 +22485,7 @@ punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -punycode@^1.2.4: +punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -22310,6 +22537,11 @@ qs@6.7.0, qs@^6.4.0, qs@^6.5.1, qs@^6.6.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= + qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -23120,24 +23352,30 @@ react-transition-group@^4.3.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react-use@^13.27.0: - version "13.27.0" - resolved "https://registry.yarnpkg.com/react-use/-/react-use-13.27.0.tgz#53a619dc9213e2cbe65d6262e8b0e76641ade4aa" - integrity sha512-2lyTyqJWyvnaP/woVtDcFS4B5pUYz0FQWI9pVHk/6TBWom2x3/ziJthkEn/LbCA9Twv39xSQU7Dn0zdIWfsNTQ== +react-universal-interface@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/react-universal-interface/-/react-universal-interface-0.6.2.tgz#5e8d438a01729a4dbbcbeeceb0b86be146fe2b3b" + integrity sha512-dg8yXdcQmvgR13RIlZbTRQOoUrDciFVoSBZILwjE2LFISxZZ8loVJKAkuzswl5js8BHda79bIb2b84ehU8IjXw== + +react-use@^15.3.4: + version "15.3.4" + resolved "https://registry.yarnpkg.com/react-use/-/react-use-15.3.4.tgz#f853d310bd71f75b38900a8caa3db93f6dc6e872" + integrity sha512-cHq1dELW6122oi1+xX7lwNyE/ugZs5L902BuO8eFJCfn2api1KeuPVG1M/GJouVARoUf54S2dYFMKo5nQXdTag== dependencies: - "@types/js-cookie" "2.2.5" - "@xobotyi/scrollbar-width" "1.9.4" + "@types/js-cookie" "2.2.6" + "@xobotyi/scrollbar-width" "1.9.5" copy-to-clipboard "^3.2.0" - fast-deep-equal "^3.1.1" + fast-deep-equal "^3.1.3" fast-shallow-equal "^1.0.0" js-cookie "^2.2.1" nano-css "^5.2.1" + react-universal-interface "^0.6.2" resize-observer-polyfill "^1.5.1" screenfull "^5.0.0" set-harmonic-interval "^1.0.1" throttle-debounce "^2.1.0" ts-easing "^0.2.0" - tslib "^1.10.0" + tslib "^2.0.0" react-virtualized-auto-sizer@^1.0.2: version "1.0.2" @@ -23605,11 +23843,16 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== -regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.3, regenerator-runtime@^0.13.4: +regenerator-runtime@^0.13.1, regenerator-runtime@^0.13.3: version "0.13.5" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz#d878a1d094b4306d10b9096484b33ebd55e26697" integrity sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA== +regenerator-runtime@^0.13.4: + version "0.13.7" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55" + integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew== + regenerator-transform@^0.14.2: version "0.14.4" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.14.4.tgz#5266857896518d1616a78a0479337a30ea974cc7" @@ -24004,7 +24247,61 @@ request-promise@^4.2.2: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.81.0, request@2.88.0, request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA= + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +request@2.88.0: + version "2.88.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" + integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== + dependencies: + aws-sign2 "~0.7.0" + aws4 "^1.8.0" + caseless "~0.12.0" + combined-stream "~1.0.6" + extend "~3.0.2" + forever-agent "~0.6.1" + form-data "~2.3.2" + har-validator "~5.1.0" + http-signature "~1.2.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.19" + oauth-sign "~0.9.0" + performance-now "^2.1.0" + qs "~6.5.2" + safe-buffer "^5.1.2" + tough-cookie "~2.4.3" + tunnel-agent "^0.6.0" + uuid "^3.3.2" + +request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -24387,9 +24684,9 @@ rsvp@^4.8.4: integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA== rtl-css-js@^1.9.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.13.1.tgz#80deabf6e8f36d6767d495cd3eb60fecb20c67e1" - integrity sha512-jgkIDj6Xi25kAEm5oYM3ZMFiOQhpLEcXi2LY/6bVr91cVz73hciHKneL5AMVPxOcks/JuizSaaNsvNRkeAWe3w== + version "1.14.0" + resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.14.0.tgz#daa4f192a92509e292a0519f4b255e6e3c076b7d" + integrity sha512-Dl5xDTeN3e7scU1cWX8c9b6/Nqz3u/HgR4gePc1kWXYiQWVQbKCEyK6+Hxve9LbcJ5EieHy1J9nJCN3grTtGwg== dependencies: "@babel/runtime" "^7.1.2" @@ -24495,7 +24792,7 @@ safefs@^4.1.0: editions "^1.1.1" graceful-fs "^4.1.4" -"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -24600,14 +24897,12 @@ scheduler@^0.18.0: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@1.0.0, schema-utils@^0.3.0, schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== +schema-utils@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" + integrity sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8= dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" + ajv "^5.0.0" schema-utils@^0.4.5: version "0.4.7" @@ -24617,6 +24912,15 @@ schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== + dependencies: + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" + schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" @@ -24645,9 +24949,9 @@ scoped-regex@^1.0.0: integrity sha1-o0a7Gs1CB65wvXwMfKnlZra63bg= screenfull@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.0.tgz#5c2010c0e84fd4157bf852877698f90b8cbe96f6" - integrity sha512-yShzhaIoE9OtOhWVyBBffA6V98CDCoyHTsp8228blmqYy1Z5bddzE/4FPiJKlr8DVR4VBiiUyfPzIQPIYDkeMA== + version "5.0.2" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-5.0.2.tgz#b9acdcf1ec676a948674df5cd0ff66b902b0bed7" + integrity sha512-cCF2b+L/mnEiORLN5xSAz6H3t18i2oHh9BA8+CQlAh5DRw2+NFAGQJOSYbcGw8B2k04g/lVvFcfZ83b3ysH5UQ== scss-tokenizer@^0.2.3: version "0.2.3" @@ -25116,6 +25420,13 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^2.0.0" +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= + dependencies: + hoek "2.x.x" + socket-location@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/socket-location/-/socket-location-1.0.0.tgz#6f0c6f891c9a61c9a750265c14921d12196d266f" @@ -25245,9 +25556,9 @@ source-map@^0.7.3: integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== sourcemap-codec@^1.4.1: - version "1.4.6" - resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.6.tgz#e30a74f0402bad09807640d39e971090a08ce1e9" - integrity sha512-1ZooVLYFxC448piVLBbtOxFcXwnymH9oUF8nRd3CuYDVvkRBxRl6pB4Mtas5a4drtL+E8LDgFkQNcgIw6tc8Hg== + version "1.4.8" + resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" + integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== space-separated-tokens@^1.0.0: version "1.1.2" @@ -25450,12 +25761,12 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -stack-generator@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.4.tgz#027513eab2b195bbb43b9c8360ba2dd0ab54de09" - integrity sha512-ha1gosTNcgxwzo9uKTQ8zZ49aUp5FIUW58YHFxCqaAHtE0XqBg0chGFYA1MfmW//x1KWq3F4G7Ug7bJh4RiRtg== +stack-generator@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/stack-generator/-/stack-generator-2.0.5.tgz#fb00e5b4ee97de603e0773ea78ce944d81596c36" + integrity sha512-/t1ebrbHkrLrDuNMdeAcsvynWgoH/i4o8EGGfX7dEYDoTXOYVAkEpFdtshlvabzc6JlJ8Kf9YdFEoz7JkzGN9Q== dependencies: - stackframe "^1.1.0" + stackframe "^1.1.1" stack-trace@0.0.10, stack-trace@0.0.x: version "0.0.10" @@ -25479,10 +25790,10 @@ stackframe@^0.3.1: resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-0.3.1.tgz#33aa84f1177a5548c8935533cbfeb3420975f5a4" integrity sha1-M6qE8Rd6VUjIk1Uzy/6zQgl19aQ= -stackframe@^1.1.0, stackframe@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.1.1.tgz#ffef0a3318b1b60c3b58564989aca5660729ec71" - integrity sha512-0PlYhdKh6AfFxRyK/v+6/k+/mMfyiEBbTM5L94D0ZytQnJ166wuwoTYLHFWGbs2dpA8Rgq763KGWmN1EQEYHRQ== +stackframe@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/stackframe/-/stackframe-1.2.0.tgz#52429492d63c62eb989804c11552e3d22e779303" + integrity sha512-GrdeshiRmS1YLMYgzF16olf2jJ/IzxXY9lhKOskuVziubpTYcYqyOwYeJKzQkwy7uN0fYSsbsC4RQaXf9LCrYA== stackman@^4.0.1: version "4.0.1" @@ -25495,22 +25806,22 @@ stackman@^4.0.1: error-callsites "^2.0.3" load-source-map "^1.0.0" -stacktrace-gps@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.3.tgz#b89f84cc13bb925b96607e737b617c8715facf57" - integrity sha512-51Rr7dXkyFUKNmhY/vqZWK+EvdsfFSRiQVtgHTFlAdNIYaDD7bVh21yBHXaNWAvTD+w+QSjxHg7/v6Tz4veExA== +stacktrace-gps@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/stacktrace-gps/-/stacktrace-gps-3.0.4.tgz#7688dc2fc09ffb3a13165ebe0dbcaf41bcf0c69a" + integrity sha512-qIr8x41yZVSldqdqe6jciXEaSCKw1U8XTXpjDuy0ki/apyTn/r3w9hDAAQOhZdxvsC93H+WwwEu5cq5VemzYeg== dependencies: source-map "0.5.6" - stackframe "^1.1.0" + stackframe "^1.1.1" stacktrace-js@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.1.tgz#ebdb0e9a16e6f171f96ca7878404e7f15c3d42ba" - integrity sha512-13oDNgBSeWtdGa4/2BycNyKqe+VktCoJ8VLx4pDoJkwGGJVtiHdfMOAj3aW9xTi8oR2v34z9IcvfCvT6XNdNAw== + version "2.0.2" + resolved "https://registry.yarnpkg.com/stacktrace-js/-/stacktrace-js-2.0.2.tgz#4ca93ea9f494752d55709a081d400fdaebee897b" + integrity sha512-Je5vBeY4S1r/RnLydLl0TBTi3F2qdfWmYsGvtfZgEI+SCprPppaIhQf5nGcal4gI4cGpCV/duLcAzT1np6sQqg== dependencies: - error-stack-parser "^2.0.4" - stack-generator "^2.0.4" - stacktrace-gps "^3.0.3" + error-stack-parser "^2.0.6" + stack-generator "^2.0.5" + stacktrace-gps "^3.0.4" state-toggle@^1.0.0: version "1.0.0" @@ -25819,6 +26130,11 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" +stringstream@~0.0.4: + version "0.0.6" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" + integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== + strip-ansi@*, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -26522,9 +26838,9 @@ throat@^5.0.0: integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA== throttle-debounce@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.1.0.tgz#257e648f0a56bd9e54fe0f132c4ab8611df4e1d5" - integrity sha512-AOvyNahXQuU7NN+VVvOOX+uW6FPaWdAOdRP5HfwYxAfCzXTFKRMoIMk+n+po318+ktcChx+F1Dd91G3YHeMKyg== + version "2.3.0" + resolved "https://registry.yarnpkg.com/throttle-debounce/-/throttle-debounce-2.3.0.tgz#fd31865e66502071e411817e241465b3e9c372e2" + integrity sha512-H7oLPV0P7+jgvrk+6mwwwBDmxTaxnu9HMXmloNLXwnNO0ZxZ31Orah2n8lU1eMPvsaowP2CX+USCgyovXfdOFQ== throttleit@^1.0.0: version "1.0.0" @@ -26866,6 +27182,21 @@ tough-cookie@^3.0.1: psl "^1.1.28" punycode "^2.1.1" +tough-cookie@~2.3.0: + version "2.3.4" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== + dependencies: + punycode "^1.4.1" + +tough-cookie@~2.4.3: + version "2.4.3" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" + integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== + dependencies: + psl "^1.1.24" + punycode "^1.4.1" + tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -27199,11 +27530,21 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.0.2, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.7.2: +typescript@4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== +typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5: + version "3.9.7" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" + integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== + +typescript@~3.7.2: + version "3.7.5" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" + integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== + ua-parser-js@^0.7.18: version "0.7.22" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3" From fc4dce598a6cb0e584d440e04b5dd4d3bfcddcdd Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Fri, 30 Oct 2020 16:04:57 +0000 Subject: [PATCH 02/15] Apply suggestions from code review Co-authored-by: Larry Gregory --- .../api_keys_page/api_keys_empty_prompt.tsx | 2 +- .../account_management/api_keys_page/api_keys_table.tsx | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx index 085121cf9e127b..49f8dd72fcb527 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx @@ -60,7 +60,7 @@ export const ApiKeysEmptyPrompt: FunctionComponent = ({

} diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx index cff43273800e2d..9a9999554c4806 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx @@ -70,6 +70,9 @@ export const ApiKeysTable: FunctionComponent = ({ = ({ }, { field: 'expiration', - name: i18n.translate('xpack.security.accountManagement.apiKeys.espiresHeader', { + name: i18n.translate('xpack.security.accountManagement.apiKeys.expiresHeader', { defaultMessage: 'Expires', }), render: (expiration: number) => From 19e038084f2e9b1311e5d378fbbb960e921f0732 Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Mon, 2 Nov 2020 10:56:21 +0000 Subject: [PATCH 03/15] Added suggestions from code review --- .../create_notifications.test.tsx | 11 --- .../notifications/create_notifications.tsx | 14 +-- .../account_management_app.test.ts | 74 --------------- .../account_management_app.test.tsx | 65 ++++++++++++++ .../account_management_app.tsx | 34 ++++--- .../api_keys_page/api_keys_empty_prompt.tsx | 31 +++---- .../api_keys_page/api_keys_page.test.tsx | 90 +++++++++++++++++++ .../api_keys_page/api_keys_page.tsx | 17 ++-- .../api_keys_page/api_keys_table.tsx | 14 +-- .../api_keys_page/create_api_key_flyout.tsx | 86 ++++++++++++------ .../invalidate_api_key_modal.tsx | 2 - .../components/doc_link.tsx | 39 ++++++++ .../api_keys/api_keys_api_client.mock.ts | 6 +- .../api_keys/api_keys_api_client.ts | 16 ++-- .../nav_control/nav_control_component.tsx | 14 ++- .../nav_control/nav_control_service.tsx | 7 +- .../security/server/routes/api_keys/create.ts | 10 +-- .../authorization/roles/model/put_payload.ts | 2 +- 18 files changed, 344 insertions(+), 188 deletions(-) delete mode 100644 x-pack/plugins/security/public/account_management/account_management_app.test.ts create mode 100644 x-pack/plugins/security/public/account_management/account_management_app.test.tsx create mode 100644 x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.test.tsx create mode 100644 x-pack/plugins/security/public/account_management/components/doc_link.tsx diff --git a/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx b/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx index 4f64a2b95f512f..23500e8480ebb8 100644 --- a/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx +++ b/src/plugins/kibana_react/public/notifications/create_notifications.test.tsx @@ -54,16 +54,12 @@ test('can display string element as title', () => { expect(notifications.toasts.add).toHaveBeenCalledTimes(1); expect(notifications.toasts.add.mock.calls[0][0]).toMatchInlineSnapshot(` Object { - "color": undefined, - "iconType": undefined, - "onClose": undefined, "text": MountPoint { "reactNode": , }, "title": MountPoint { "reactNode": "foo", }, - "toastLifeTimeMs": undefined, } `); }); @@ -120,7 +116,6 @@ test('can set toast properties', () => { Object { "color": "danger", "iconType": "foo", - "onClose": undefined, "text": MountPoint { "reactNode": 1 @@ -147,42 +142,36 @@ test('can display success, warning and danger toasts', () => { Object { "color": "success", "iconType": "check", - "onClose": undefined, "text": MountPoint { "reactNode": , }, "title": MountPoint { "reactNode": "1", }, - "toastLifeTimeMs": undefined, } `); expect(notifications.toasts.add.mock.calls[1][0]).toMatchInlineSnapshot(` Object { "color": "warning", "iconType": "help", - "onClose": undefined, "text": MountPoint { "reactNode": , }, "title": MountPoint { "reactNode": "2", }, - "toastLifeTimeMs": undefined, } `); expect(notifications.toasts.add.mock.calls[2][0]).toMatchInlineSnapshot(` Object { "color": "danger", "iconType": "alert", - "onClose": undefined, "text": MountPoint { "reactNode": , }, "title": MountPoint { "reactNode": "3", }, - "toastLifeTimeMs": undefined, } `); }); diff --git a/src/plugins/kibana_react/public/notifications/create_notifications.tsx b/src/plugins/kibana_react/public/notifications/create_notifications.tsx index 826435ec5b587a..dc6430e2f18a95 100644 --- a/src/plugins/kibana_react/public/notifications/create_notifications.tsx +++ b/src/plugins/kibana_react/public/notifications/create_notifications.tsx @@ -23,24 +23,14 @@ import { KibanaReactNotifications } from './types'; import { toMountPoint } from '../util'; export const createNotifications = (services: KibanaServices): KibanaReactNotifications => { - const show: KibanaReactNotifications['toasts']['show'] = ({ - title, - body, - color, - iconType, - toastLifeTimeMs, - onClose, - }) => { + const show: KibanaReactNotifications['toasts']['show'] = ({ title, body, ...rest }) => { if (!services.notifications) { throw new TypeError('Could not show notification as notifications service is not available.'); } services.notifications!.toasts.add({ title: toMountPoint(title), text: toMountPoint(<>{body || null}), - color, - iconType, - toastLifeTimeMs, - onClose, + ...rest, }); }; diff --git a/x-pack/plugins/security/public/account_management/account_management_app.test.ts b/x-pack/plugins/security/public/account_management/account_management_app.test.ts deleted file mode 100644 index c41bd43872beed..00000000000000 --- a/x-pack/plugins/security/public/account_management/account_management_app.test.ts +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -jest.mock('./account_management_page'); - -import { AppMount, AppNavLinkStatus } from 'src/core/public'; -import { UserAPIClient } from '../management'; -import { accountManagementApp } from './account_management_app'; - -import { coreMock, scopedHistoryMock } from '../../../../../src/core/public/mocks'; -import { securityMock } from '../mocks'; - -describe('accountManagementApp', () => { - it('properly registers application', () => { - const coreSetupMock = coreMock.createSetup(); - - accountManagementApp.create({ - application: coreSetupMock.application, - getStartServices: coreSetupMock.getStartServices, - authc: securityMock.createSetup().authc, - }); - - expect(coreSetupMock.application.register).toHaveBeenCalledTimes(1); - - const [[appRegistration]] = coreSetupMock.application.register.mock.calls; - expect(appRegistration).toEqual({ - id: 'security_account', - appRoute: '/security/account', - navLinkStatus: AppNavLinkStatus.hidden, - title: 'Account Management', - mount: expect.any(Function), - }); - }); - - it('properly sets breadcrumbs and renders application', async () => { - const coreSetupMock = coreMock.createSetup(); - const coreStartMock = coreMock.createStart(); - coreSetupMock.getStartServices.mockResolvedValue([coreStartMock, {}, {}]); - - const authcMock = securityMock.createSetup().authc; - const containerMock = document.createElement('div'); - - accountManagementApp.create({ - application: coreSetupMock.application, - getStartServices: coreSetupMock.getStartServices, - authc: authcMock, - }); - - const [[{ mount }]] = coreSetupMock.application.register.mock.calls; - await (mount as AppMount)({ - element: containerMock, - appBasePath: '', - onAppLeave: jest.fn(), - setHeaderActionMenu: jest.fn(), - history: scopedHistoryMock.create(), - }); - - expect(coreStartMock.chrome.setBreadcrumbs).toHaveBeenCalledTimes(1); - expect(coreStartMock.chrome.setBreadcrumbs).toHaveBeenCalledWith([ - { text: 'Account Management' }, - ]); - - const mockRenderApp = jest.requireMock('./account_management_page').renderAccountManagementPage; - expect(mockRenderApp).toHaveBeenCalledTimes(1); - expect(mockRenderApp).toHaveBeenCalledWith(coreStartMock.i18n, containerMock, { - userAPIClient: expect.any(UserAPIClient), - authc: authcMock, - notifications: coreStartMock.notifications, - }); - }); -}); diff --git a/x-pack/plugins/security/public/account_management/account_management_app.test.tsx b/x-pack/plugins/security/public/account_management/account_management_app.test.tsx new file mode 100644 index 00000000000000..c5e7784e46fa43 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/account_management_app.test.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import ReactDOM from 'react-dom'; +import { act, screen, fireEvent } from '@testing-library/react'; +import { AppMount, AppNavLinkStatus, ScopedHistory } from '../../../../../src/core/public'; +import { coreMock } from '../../../../../src/core/public/mocks'; +import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; +import { securityMock } from '../mocks'; +import { accountManagementApp } from './account_management_app'; +import { createMemoryHistory } from 'history'; + +describe('accountManagementApp', () => { + it('registers application', () => { + const { application, getStartServices } = coreMock.createSetup(); + const { authc } = securityMock.createSetup(); + accountManagementApp.create({ application, getStartServices, authc }); + expect(application.register).toHaveBeenLastCalledWith({ + id: 'security_account', + appRoute: '/security/account', + navLinkStatus: AppNavLinkStatus.hidden, + title: 'Account Management', + mount: expect.any(Function), + }); + }); + + it('renders application and sets breadcrumbs', async () => { + const { application, getStartServices } = coreMock.createSetup(); + const coreStartMock = coreMock.createStart(); + getStartServices.mockResolvedValue([coreStartMock, {}, {}]); + const { authc } = securityMock.createSetup(); + authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser({ username: 'some-user', full_name: undefined }) + ); + accountManagementApp.create({ application, getStartServices, authc }); + const [[{ mount }]] = application.register.mock.calls; + const element = document.body.appendChild(document.createElement('div')); + + await act(async () => { + await (mount as AppMount)({ + element, + appBasePath: '', + onAppLeave: jest.fn(), + setHeaderActionMenu: jest.fn(), + history: (createMemoryHistory() as unknown) as ScopedHistory, + }); + }); + expect(coreStartMock.chrome.setBreadcrumbs).toHaveBeenLastCalledWith([ + expect.objectContaining({ text: 'Account Management' }), + ]); + + fireEvent.click(screen.getByRole('tab', { name: 'API Keys' })); + expect(coreStartMock.chrome.setBreadcrumbs).toHaveBeenLastCalledWith([ + expect.objectContaining({ text: 'Account Management' }), + expect.objectContaining({ text: 'API Keys' }), + ]); + + // Need to cleanup manually since `mount` renders the app straight to the DOM + ReactDOM.unmountComponentAtNode(element); + document.body.removeChild(element); + }); +}); diff --git a/x-pack/plugins/security/public/account_management/account_management_app.tsx b/x-pack/plugins/security/public/account_management/account_management_app.tsx index 03b7f7be1e7ab9..cf7ce75938e0ed 100644 --- a/x-pack/plugins/security/public/account_management/account_management_app.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_app.tsx @@ -7,6 +7,7 @@ import React, { lazy, Suspense, FunctionComponent } from 'react'; import ReactDOM from 'react-dom'; import { Router, useHistory } from 'react-router-dom'; +import { History } from 'history'; import { useAsync } from 'react-use'; import { i18n } from '@kbn/i18n'; import { @@ -29,9 +30,10 @@ import { AppNavLinkStatus, StartServicesAccessor, NotificationsSetup, + CoreStart, } from '../../../../../src/core/public'; import { - createKibanaReactContext, + KibanaContextProvider, reactRouterNavigate, } from '../../../../../src/plugins/kibana_react/public'; import { getUserDisplayName } from '../../common/model'; @@ -60,19 +62,12 @@ export const accountManagementApp = Object.freeze({ async mount({ element, history }: AppMountParameters) { const [coreStart] = await getStartServices(); - const { Provider: KibanaProvider } = createKibanaReactContext(coreStart); - const { Context: IntlProvider } = coreStart.i18n; - ReactDOM.render( - - - - - - - - - , + + + + + , element ); @@ -82,6 +77,19 @@ export const accountManagementApp = Object.freeze({ }, }); +export interface ProvidersProps { + services: CoreStart; + history: History; +} + +export const Providers: FunctionComponent = ({ services, history, children }) => ( + + + {children} + + +); + export interface AccountManagementProps { authc: AuthenticationServiceSetup; notifications: NotificationsSetup; diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx index 085121cf9e127b..f9e9dd0af1441d 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_empty_prompt.tsx @@ -5,9 +5,9 @@ */ import React, { FunctionComponent } from 'react'; -import { EuiLink, EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; +import { DocLink } from '../components/doc_link'; export interface ApiKeysEmptyPromptProps { error?: Error; @@ -17,15 +17,11 @@ export const ApiKeysEmptyPrompt: FunctionComponent = ({ error, children, }) => { - const { services } = useKibana(); - if (error) { - const { statusCode, message = '' } = (error as any).body ?? {}; - - if (statusCode === 400 && message.indexOf('[feature_not_enabled_exception]') !== -1) { + if (doesErrorIndicateAPIKeysAreDisabled(error)) { return (

@@ -35,16 +31,12 @@ export const ApiKeysEmptyPrompt: FunctionComponent = ({ />

- + - +

} @@ -52,7 +44,7 @@ export const ApiKeysEmptyPrompt: FunctionComponent = ({ ); } - if (statusCode === 403) { + if (doesErrorIndicateUserHasNoPermissionsToManageAPIKeys(error)) { return ( = ({ /> ); }; + +function doesErrorIndicateAPIKeysAreDisabled(error: Record) { + const message = error.body?.message || ''; + return message.indexOf('disabled.feature="api_keys"') !== -1; +} + +function doesErrorIndicateUserHasNoPermissionsToManageAPIKeys(error: Record) { + return error.body?.statusCode === 403; +} diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.test.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.test.tsx new file mode 100644 index 00000000000000..44728f4d674977 --- /dev/null +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.test.tsx @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, fireEvent, within, waitForElementToBeRemoved } from '@testing-library/react'; +import { createMemoryHistory } from 'history'; +import { coreMock } from '../../../../../../src/core/public/mocks'; +import { Providers } from '../account_management_app'; +import { ApiKeysPage } from './api_keys_page'; + +const getApiKeysResponse = { + apiKeys: [ + { + creation: 1571322182082, + expiration: 1571408582082, + id: '0QQZ2m0BO2XZwgJFuWTT', + invalidated: false, + name: 'my-api-key', + realm: 'reserved', + username: 'elastic', + }, + ], +}; + +describe('ApiKeysPage', () => { + it('fetches API keys on mount', async () => { + const coreStart = coreMock.createStart(); + const history = createMemoryHistory(); + coreStart.http.get.mockResolvedValue(getApiKeysResponse); + + const { queryByText } = render( + + + + ); + + expect(coreStart.http.get).toHaveBeenLastCalledWith('/internal/security/api_key', { + query: { isAdmin: false }, + }); + + await waitForElementToBeRemoved(queryByText(/Loading API keys/i)); + }); + + it('creates API key on form submit', async () => { + const coreStart = coreMock.createStart(); + const history = createMemoryHistory({ initialEntries: ['/api-keys/create'] }); + coreStart.http.get.mockResolvedValue(getApiKeysResponse); + + const { findByRole } = render( + + + + ); + + const flyout = await findByRole('dialog'); + fireEvent.change(within(flyout).getByLabelText(/Name/i), { target: { value: 'Test Key' } }); + fireEvent.click(within(flyout).getByRole('button', { name: /Create API key/i })); + + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key', { + body: '{"name":"Test Key"}', + }); + + await waitForElementToBeRemoved(() => within(flyout).queryByText(/Creating API key/i)); + }); + + it('invalidates API key after confirmation', async () => { + const coreStart = coreMock.createStart(); + const history = createMemoryHistory(); + coreStart.http.get.mockResolvedValue(getApiKeysResponse); + + const { findByRole, getByRole, queryByText } = render( + + + + ); + + const table = await findByRole('table'); + fireEvent.click(within(table).getByRole('button', { name: /Invalidate/i })); + fireEvent.click(getByRole('button', { name: /Invalidate this key/i })); + + expect(coreStart.http.post).toHaveBeenLastCalledWith('/internal/security/api_key/invalidate', { + body: '{"apiKeys":[{"id":"0QQZ2m0BO2XZwgJFuWTT","name":"my-api-key"}],"isAdmin":false}', + }); + + await waitForElementToBeRemoved(() => queryByText(/Invalidating API key/i)); + }); +}); diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx index 5a364a2c3a353d..7f70ab228cb0f9 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FunctionComponent, useState, useEffect } from 'react'; +import React, { FunctionComponent, useState } from 'react'; import { Route, useHistory } from 'react-router-dom'; -import { useAsyncFn } from 'react-use'; +import { useAsyncFn, useMount } from 'react-use'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; @@ -27,20 +27,19 @@ import { ApiKeysEmptyPrompt } from './api_keys_empty_prompt'; export const ApiKeysPage: FunctionComponent = () => { const history = useHistory(); const { services } = useKibana(); - const apiKeysApiClient = new APIKeysAPIClient(services.http!); - const [state, getApiKeys] = useAsyncFn(apiKeysApiClient.getApiKeys, [services.http]); + const [state, getApiKeys] = useAsyncFn(() => new APIKeysAPIClient(services.http!).getApiKeys(), [ + services.http, + ]); const [createdApiKey, setCreatedApiKey] = useState(); const [apiKeyToInvalidate, setApiKeyToInvalidate] = useState(); - useEffect(() => { - getApiKeys(); - }, []); // eslint-disable-line react-hooks/exhaustive-deps + useMount(getApiKeys); if (!state.value) { if (state.error && !state.loading) { return ( - getApiKeys()}> + { = ({ { - onInvalidateItem(item); - }} + onClick={() => onInvalidateItem(item)} /> @@ -51,9 +55,7 @@ export const ApiKeysTable: FunctionComponent = ({ color="danger" flush="right" disabled={props.loading} - onClick={() => { - onInvalidateItem(item); - }} + onClick={() => onInvalidateItem(item)} > = ({ initialFocus={nameInput} onSubmit={eventHandlers.onSubmit} onClose={onClose} + size="s" > {form.submitError && ( <> @@ -100,7 +103,9 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ label={i18n.translate('xpack.security.accountManagement.createApiKey.nameLabel', { defaultMessage: 'Name', })} - helpText="What is the API key used for?" + helpText={i18n.translate('xpack.security.accountManagement.createApiKey.nameHelpText', { + defaultMessage: 'What is the API key used for?', + })} error={form.errors.name} isInvalid={!!form.errors.name} > @@ -109,6 +114,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ defaultValue={form.values.name} isInvalid={!!form.errors.name} inputRef={nameInput} + fullWidth /> @@ -121,10 +127,10 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ defaultMessage: 'Grant subset of your permissions', } )} - checked={!!form.values.customPriviliges} - onChange={(e) => form.setValue('customPriviliges', e.target.checked)} + checked={!!form.values.customPrivileges} + onChange={(e) => form.setValue('customPrivileges', e.target.checked)} /> - {form.values.customPriviliges && ( + {form.values.customPrivileges && ( <> = ({ defaultMessage: 'Role Descriptors', } )} - helpText="https://www.elastic.co/guide/en/elasticsearch/reference/current/security-api-create-api-key.html#security-api-create-api-key-request-body" + helpText={ + + + + } error={form.errors.role_descriptors} isInvalid={!!form.errors.role_descriptors} > - form.setValue('role_descriptors', value)} - onBlur={() => form.trigger('role_descriptors')} - value={form.values.role_descriptors} - height="300px" - mode="json" - /> + + form.setValue('role_descriptors', value)} + width="100%" + height="300px" + languageId="xjson" + options={{ + fixedOverflowWidgets: true, + folding: false, + lineNumbers: 'off', + scrollBeyondLastLine: false, + minimap: { + enabled: false, + }, + scrollbar: { + useShadows: false, + }, + wordBasedSuggestions: false, + wordWrap: 'on', + wrappingIndent: 'indent', + }} + /> + )} @@ -167,7 +200,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ = ({ name="expiration" defaultValue={form.values.expiration} isInvalid={!!form.errors.expiration} + fullWidth /> @@ -192,12 +226,12 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ CreateApiKeyFlyout.defaultProps = { defaultValues: { customExpiration: false, - customPriviliges: false, + customPrivileges: false, role_descriptors: JSON.stringify( { 'role-a': { cluster: ['all'], - index: [ + indices: [ { names: ['index-a*'], privileges: ['read'], @@ -206,7 +240,7 @@ CreateApiKeyFlyout.defaultProps = { }, 'role-b': { cluster: ['all'], - index: [ + indices: [ { names: ['index-b*'], privileges: ['all'], @@ -224,22 +258,24 @@ export function validate(v: Partial) { const errors: ValidationErrors = {}; if (!v.name) { - errors.name = 'Enter a name.'; + errors.name = i18n.translate('xpack.security.management.apiKeys.createApiKey.nameRequired', { + defaultMessage: 'Enter a name.', + }); } if (v.customExpiration && !v.expiration) { errors.expiration = i18n.translate( - 'xpack.security.management.apiKeys.createApiKey.successNotification', + 'xpack.security.management.apiKeys.createApiKey.expirationRequired', { defaultMessage: 'Enter a duration or disable the option.', } ); } - if (v.customPriviliges) { + if (v.customPrivileges) { if (!v.role_descriptors) { errors.role_descriptors = i18n.translate( - 'xpack.security.management.apiKeys.createApiKey.successNotification', + 'xpack.security.management.apiKeys.createApiKey.roleDescriptorsRequired', { defaultMessage: 'Enter role descriptors or disable the option.', } @@ -249,7 +285,7 @@ export function validate(v: Partial) { JSON.parse(v.role_descriptors); } catch (e) { errors.role_descriptors = i18n.translate( - 'xpack.security.management.apiKeys.createApiKey.successNotification', + 'xpack.security.management.apiKeys.createApiKey.invalidJsonError', { defaultMessage: 'Enter valid JSON.', } @@ -266,7 +302,7 @@ export function mapValues(values: FormValues): CreateApiKeyRequest { name: values.name, expiration: values.customExpiration && values.expiration ? `${values.expiration}d` : undefined, role_descriptors: - values.customPriviliges && values.role_descriptors + values.customPrivileges && values.role_descriptors ? JSON.parse(values.role_descriptors) : undefined, }; diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx index 11c16cea33b3b8..2150826c53d724 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/invalidate_api_key_modal.tsx @@ -47,7 +47,6 @@ export const InvalidateApiKeyModal: FunctionComponent string; + +export function useDocLinks(): [DocLinks, GetDocLinkFunction] { + const { services } = useKibana(); + const { links, ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } = services.docLinks!; + const getDocLink = useCallback( + (app, doc) => { + return `${ELASTIC_WEBSITE_URL}guide/en/${app}/reference/${DOC_LINK_VERSION}/${doc}`; + }, + [ELASTIC_WEBSITE_URL, DOC_LINK_VERSION] + ); + return [links, getDocLink]; +} + +export interface DocLinkProps { + app: string; + doc: string; +} + +export const DocLink: FunctionComponent = ({ app, doc, children }) => { + const [, getDocLink] = useDocLinks(); + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts index 2a45d497029f41..46551cae2bdc46 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts @@ -4,10 +4,14 @@ * you may not use this file except in compliance with the Elastic License. */ +import type { PublicMethodsOf } from '@kbn/utility-types'; +import { APIKeysAPIClient } from './api_keys_api_client'; + export const apiKeysAPIClientMock = { - create: () => ({ + create: (): PublicMethodsOf => ({ checkPrivileges: jest.fn(), getApiKeys: jest.fn(), invalidateApiKeys: jest.fn(), + createApiKey: jest.fn(), }), }; diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts index ac8655a7e49a50..7d78a5ae5e9aa8 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts @@ -42,23 +42,23 @@ const apiKeysUrl = '/internal/security/api_key'; export class APIKeysAPIClient { constructor(private readonly http: HttpStart) {} - public checkPrivileges = async () => { + public async checkPrivileges() { return await this.http.get(`${apiKeysUrl}/privileges`); - }; + } - public getApiKeys = async (isAdmin = false) => { + public async getApiKeys(isAdmin = false) { return await this.http.get(apiKeysUrl, { query: { isAdmin } }); - }; + } - public invalidateApiKeys = async (apiKeys: ApiKeyToInvalidate[], isAdmin = false) => { + public async invalidateApiKeys(apiKeys: ApiKeyToInvalidate[], isAdmin = false) { return await this.http.post(`${apiKeysUrl}/invalidate`, { body: JSON.stringify({ apiKeys, isAdmin }), }); - }; + } - public createApiKey = async (apiKey: CreateApiKeyRequest) => { + public async createApiKey(apiKey: CreateApiKeyRequest) { return await this.http.post(apiKeysUrl, { body: JSON.stringify(apiKey), }); - }; + } } diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index 3ddabb0dc55f8c..b9a0d5ec79a9f4 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable @elastic/eui/href-or-on-click */ + import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react'; @@ -120,7 +122,11 @@ export class SecurityNavControl extends Component { - + { - + - + + + , el ); diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts index dfef44e9b6d92f..25e3fdc1b76900 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.ts @@ -6,15 +6,10 @@ import { schema } from '@kbn/config-schema'; import { createLicensedRouteHandler } from '../licensed_route_handler'; -import { ApiKey } from '../../../common/model'; -import { wrapError, wrapIntoCustomErrorResponse } from '../../errors'; +import { wrapIntoCustomErrorResponse } from '../../errors'; +import { elasticsearchRoleSchema } from '../authorization/roles/model/put_payload'; import { RouteDefinitionParams } from '..'; -interface ResponseType { - itemsInvalidated: Array>; - errors: Array & { error: Error }>; -} - export function defineCreateApiKeyRoutes({ router, clusterClient }: RouteDefinitionParams) { router.post( { @@ -23,6 +18,7 @@ export function defineCreateApiKeyRoutes({ router, clusterClient }: RouteDefinit body: schema.object({ name: schema.string(), expiration: schema.maybe(schema.string()), + role_descriptors: schema.maybe(schema.recordOf(schema.string(), elasticsearchRoleSchema)), }), }, }, diff --git a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts index e0af14f90d01c5..5660dfe5057582 100644 --- a/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts +++ b/x-pack/plugins/security/server/routes/authorization/roles/model/put_payload.ts @@ -15,7 +15,7 @@ import { ElasticsearchRole } from './elasticsearch_role'; * Elasticsearch specific portion of the role definition. * See more details at https://www.elastic.co/guide/en/elasticsearch/reference/master/security-api.html#security-role-apis. */ -const elasticsearchRoleSchema = schema.object({ +export const elasticsearchRoleSchema = schema.object({ /** * An optional list of cluster privileges. These privileges define the cluster level actions that * users with this role are able to execute From 1fc64ab9c97b1fa062c6f0ea123ae3ccc9638e10 Mon Sep 17 00:00:00 2001 From: Thom Heymann <190132+thomheymann@users.noreply.github.com> Date: Mon, 2 Nov 2020 10:56:33 +0000 Subject: [PATCH 04/15] Apply suggestions from code review Co-authored-by: Larry Gregory --- .../account_management/api_keys_page/create_api_key_flyout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx index faafc6809e349c..9af6d0eac750db 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx @@ -29,7 +29,7 @@ export interface FormValues { name: string; expiration: string; customExpiration: boolean; - customPriviliges: boolean; + customPrivileges: boolean; role_descriptors: string; } From ad3907eeaa18cb1a5d95184b748cebf59f19c1df Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Mon, 2 Nov 2020 11:54:58 +0000 Subject: [PATCH 05/15] Updated lock file and create route handler --- .../security/server/routes/api_keys/create.ts | 15 +- yarn.lock | 344 ++---------------- 2 files changed, 32 insertions(+), 327 deletions(-) diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts index 25e3fdc1b76900..81b4ac2cbf505c 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.ts @@ -10,7 +10,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; import { elasticsearchRoleSchema } from '../authorization/roles/model/put_payload'; import { RouteDefinitionParams } from '..'; -export function defineCreateApiKeyRoutes({ router, clusterClient }: RouteDefinitionParams) { +export function defineCreateApiKeyRoutes({ router, authc }: RouteDefinitionParams) { router.post( { path: '/internal/security/api_key', @@ -18,16 +18,19 @@ export function defineCreateApiKeyRoutes({ router, clusterClient }: RouteDefinit body: schema.object({ name: schema.string(), expiration: schema.maybe(schema.string()), - role_descriptors: schema.maybe(schema.recordOf(schema.string(), elasticsearchRoleSchema)), + role_descriptors: schema.recordOf(schema.string(), elasticsearchRoleSchema, { + defaultValue: {}, + }), }), }, }, createLicensedRouteHandler(async (context, request, response) => { - const scopedClusterClient = clusterClient.asScoped(request); try { - const apiKey = await scopedClusterClient.callAsCurrentUser('shield.createAPIKey', { - body: request.body, - }); + const apiKey = await authc.createAPIKey(request, request.body); + + if (!apiKey) { + return response.badRequest({ body: { message: `API Keys are not available` } }); + } return response.ok({ body: apiKey }); } catch (error) { diff --git a/yarn.lock b/yarn.lock index bd64b8cdd07e14..0e44dbfebb06e4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5761,7 +5761,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.1.tgz#ef916e271c64ac12171fd8384eaae6b2345854da" integrity sha512-RO1ibKvd27e6FEShVFfPALuHI3WjSVNeK5FIsmme/LYRNxjKuNj+Dt7bucLa6NdSv3JcVTyMlm9kGR84z1XpaQ== -ajv@^4.7.0, ajv@^4.9.1: +ajv@^4.7.0: version "4.11.8" resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" integrity sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY= @@ -5769,16 +5769,6 @@ ajv@^4.7.0, ajv@^4.9.1: co "^4.6.0" json-stable-stringify "^1.0.1" -ajv@^5.0.0: - version "5.5.2" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" - integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= - dependencies: - co "^4.6.0" - fast-deep-equal "^1.0.0" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.3.0" - ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.5.5, ajv@^6.9.1: version "6.12.4" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.4.tgz#0614facc4522127fa713445c6bfd3ebd376e2234" @@ -5789,16 +5779,6 @@ ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.2, ajv@^6.12.4, ajv@^6.5.5, ajv@ json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^6.12.3: - version "6.12.6" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" - integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== - dependencies: - fast-deep-equal "^3.1.1" - fast-json-stable-stringify "^2.0.0" - json-schema-traverse "^0.4.1" - uri-js "^4.2.2" - align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -6587,11 +6567,6 @@ assert-plus@1.0.0, assert-plus@^1.0.0: resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= -assert-plus@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" - integrity sha1-104bh+ev/A24qttwIfP+SBAasjQ= - assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" @@ -6795,21 +6770,11 @@ await-event@^2.1.0: resolved "https://registry.yarnpkg.com/await-event/-/await-event-2.1.0.tgz#78e9f92684bae4022f9fa0b5f314a11550f9aa76" integrity sha1-eOn5JoS65AIvn6C18xShFVD5qnY= -aws-sign2@~0.6.0: - version "0.6.0" - resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" - integrity sha1-FDQt0428yU0OW4fXY81jYSwOeU8= - aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.2.1: - version "1.10.1" - resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.10.1.tgz#e1e82e4f3e999e2cfd61b161280d16a111f86428" - integrity sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA== - aws4@^1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" @@ -7599,13 +7564,6 @@ boolbase@^1.0.0, boolbase@~1.0.0: resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24= -boom@2.x.x: - version "2.10.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" - integrity sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8= - dependencies: - hoek "2.x.x" - boom@7.x.x, boom@^7.1.0, boom@^7.2.0: version "7.2.2" resolved "https://registry.yarnpkg.com/boom/-/boom-7.2.2.tgz#ac92101451aa5cea901aed07d881dd32b4f08345" @@ -9022,7 +8980,7 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -combined-stream@^1.0.5, combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.5, combined-stream@~1.0.6: +combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6: version "1.0.8" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f" integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== @@ -9627,13 +9585,6 @@ crypt@~0.0.1: resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs= -cryptiles@2.x.x: - version "2.0.5" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" - integrity sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g= - dependencies: - boom "2.x.x" - cryptiles@4.x.x: version "4.1.3" resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-4.1.3.tgz#2461d3390ea0b82c643a6ba79f0ed491b0934c25" @@ -10465,12 +10416,7 @@ deep-object-diff@^1.1.0: resolved "https://registry.yarnpkg.com/deep-object-diff/-/deep-object-diff-1.1.0.tgz#d6fabf476c2ed1751fc94d5ca693d2ed8c18bc5a" integrity sha512-b+QLs5vHgS+IoSNcUE4n9HP2NwcHj7aqnJWsjPtuG75Rh5TOaGt0OjAYInh77d5T16V5cRDC+Pw/6ZZZiETBGw== -deepmerge@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-3.2.0.tgz#58ef463a57c08d376547f8869fdc5bcee957f44e" - integrity sha512-6+LuZGU7QCNUnAJyX8cIrlzoEgggTM6B7mm+znKOX4t5ltluT9KLjN6g61ECMS0LTsLW7yDpNoxhix5FZcrIow== - -deepmerge@^4.0.0, deepmerge@^4.2.2: +deepmerge@3.2.0, deepmerge@^4.0.0, deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== @@ -11411,13 +11357,6 @@ encodeurl@^1.0.2, encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -encoding@^0.1.11: - version "0.1.13" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" - integrity sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A== - dependencies: - iconv-lite "^0.6.2" - end-of-stream@^1.0.0, end-of-stream@^1.1.0, end-of-stream@^1.4.1, end-of-stream@^1.4.4: version "1.4.4" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.4.tgz#5ae64a5f45057baf3626ec14da0ca5e4b2431eb0" @@ -12478,7 +12417,7 @@ extend-shallow@^3.0.0, extend-shallow@^3.0.2: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@^3.0.0, extend@~3.0.0, extend@~3.0.2: +extend@^3.0.0, extend@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== @@ -12578,21 +12517,11 @@ fancy-log@^1.3.2: color-support "^1.1.3" time-stamp "^1.0.0" -fast-deep-equal@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" - integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= - -fast-deep-equal@^3.1.1: +fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3, fast-deep-equal@~3.1.3: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz#545145077c501491e33b15ec408c294376e94ae4" integrity sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA== -fast-deep-equal@^3.1.3, fast-deep-equal@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" - integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== - fast-diff@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03" @@ -13267,15 +13196,6 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" -form-data@~2.1.1: - version "2.1.4" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" - integrity sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE= - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - form-data@~2.3.2: version "2.3.3" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" @@ -14805,32 +14725,11 @@ hapi@^17.5.3: teamwork "3.x.x" topo "3.x.x" -har-schema@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" - integrity sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4= - har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= -har-validator@~4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" - integrity sha1-M0gdDxu/9gDdID11gSpqX7oALio= - dependencies: - ajv "^4.9.1" - har-schema "^1.0.5" - -har-validator@~5.1.0: - version "5.1.5" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd" - integrity sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w== - dependencies: - ajv "^6.12.3" - har-schema "^2.0.0" - har-validator@~5.1.3: version "5.1.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080" @@ -15132,16 +15031,6 @@ hat@0.0.3: resolved "https://registry.yarnpkg.com/hat/-/hat-0.0.3.tgz#bb014a9e64b3788aed8005917413d4ff3d502d8a" integrity sha1-uwFKnmSzeIrtgAWRdBPU/z1QLYo= -hawk@~3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" - integrity sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ= - dependencies: - boom "2.x.x" - cryptiles "2.x.x" - hoek "2.x.x" - sntp "1.x.x" - he@1.2.0, he@1.2.x, he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -15210,11 +15099,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoek@2.x.x: - version "2.16.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" - integrity sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0= - hoek@5.x.x, hoek@^5.0.4: version "5.0.4" resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da" @@ -15225,12 +15109,7 @@ hoek@6.x.x: resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.0.3.tgz#7884360426d927865a0a1251fc9c59313af5b798" integrity sha512-TU6RyZ/XaQCTWRLrdqZZtZqwxUVr6PDMfi6MlWNURZ7A6czanQqX4pFE1mdOUQR9FdPCsZ0UzL8jI/izZ+eBSQ== -hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5: - version "2.5.5" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" - integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== - -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: +hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react-statics@^2.5.5, hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.1.0, hoist-non-react-statics@^3.3.0, hoist-non-react-statics@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== @@ -15485,15 +15364,6 @@ http-request-to-url@^1.0.0: await-event "^2.1.0" socket-location "^1.0.0" -http-signature@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" - integrity sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8= - dependencies: - assert-plus "^0.2.0" - jsprim "^1.2.2" - sshpk "^1.7.0" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -15587,13 +15457,6 @@ iconv-lite@^0.5.0, iconv-lite@^0.5.1: dependencies: safer-buffer ">= 2.1.2 < 3" -iconv-lite@^0.6.2: - version "0.6.2" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.2.tgz#ce13d1875b0c3a674bd6a04b7f76b01b1b6ded01" - integrity sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ== - dependencies: - safer-buffer ">= 2.1.2 < 3.0.0" - icss-utils@^4.0.0, icss-utils@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-4.1.1.tgz#21170b53789ee27447c2f47dd683081403f9a467" @@ -16660,7 +16523,7 @@ is-ssh@^1.3.0: dependencies: protocols "^1.1.0" -is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0: +is-stream@^1.0.0, is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= @@ -17740,11 +17603,6 @@ json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== -json-schema-traverse@^0.3.0: - version "0.3.1" - resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" - integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= - json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" @@ -18752,17 +18610,7 @@ lodash.uniq@4.5.0, lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@4.17.11: - version "4.17.11" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" - integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== - -lodash@4.17.15: - version "4.17.15" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548" - integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A== - -lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: +lodash@4.17.11, lodash@4.17.15, lodash@>4.17.4, lodash@^4, lodash@^4.0.0, lodash@^4.0.1, lodash@^4.10.0, lodash@^4.11.1, lodash@^4.15.0, lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.2, lodash@^4.17.20, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0, lodash@~4.17.10, lodash@~4.17.15, lodash@~4.17.5: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== @@ -19482,7 +19330,7 @@ mime-db@1.44.0, mime-db@1.x.x, "mime-db@>= 1.40.0 < 2": resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.44.0.tgz#fa11c5eb0aca1334b4233cb4d52f10c5a6272f92" integrity sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg== -mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24, mime-types@~2.1.7: +mime-types@^2.1.12, mime-types@^2.1.25, mime-types@^2.1.26, mime-types@~2.1.17, mime-types@~2.1.19, mime-types@~2.1.24: version "2.1.27" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.27.tgz#47949f98e279ea53119f5722e0f34e529bec009f" integrity sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w== @@ -19583,22 +19431,7 @@ minimist-options@^4.0.2: is-plain-obj "^1.1.0" kind-of "^6.0.3" -minimist@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" - integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= - -minimist@1.1.x: - version "1.1.3" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.1.3.tgz#3bedfd91a92d39016fcfaa1c681e8faa1a1efda8" - integrity sha1-O+39kaktOQFvz6ocaB6Pqhoe/ag= - -minimist@1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" - integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= - -minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: +minimist@0.0.8, minimist@1.1.x, minimist@1.2.0, minimist@^1.1.0, minimist@^1.1.1, minimist@^1.1.3, minimist@^1.2.0, minimist@^1.2.5, minimist@~1.2.0: version "1.2.5" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== @@ -20225,20 +20058,7 @@ node-environment-flags@1.0.6: object.getownpropertydescriptors "^2.0.3" semver "^5.7.0" -node-fetch@2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.1.2.tgz#ab884e8e7e57e38a944753cec706f788d1768bb5" - integrity sha1-q4hOjn5X44qUR1POxwb3iNF2i7U= - -node-fetch@^1.0.1: - version "1.7.3" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" - integrity sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ== - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: +node-fetch@2.1.2, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== @@ -20248,16 +20068,11 @@ node-forge@0.9.0: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579" integrity sha512-7ASaDa3pD+lJ3WvXFsxekJQelBKRpne+GOVbLbtHYdd7pFspyeuJHnWfLplGf3SwKGbfs/aYl5V/JCIaHVUKKQ== -node-forge@^0.10.0: +node-forge@^0.10.0, node-forge@^0.7.6: version "0.10.0" resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.10.0.tgz#32dea2afb3e9926f02ee5ce8794902691a676bf3" integrity sha512-PPmu8eEeG9saEUvI97fm4OYxXVB6bFvyNTyiUOBichBpFG8A1Ljw3bY62+5oOjDEMHRnd0Y7HQ+x7uzxOzC6JA== -node-forge@^0.7.6: - version "0.7.6" - resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" - integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== - node-gyp-build@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-4.2.3.tgz#ce6277f853835f718829efb47db20f3e4d9c4739" @@ -20681,11 +20496,6 @@ nyc@^15.0.1: test-exclude "^6.0.0" yargs "^15.0.2" -oauth-sign@~0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" - integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= - oauth-sign@~0.9.0: version "0.9.0" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" @@ -22419,11 +22229,6 @@ pseudomap@^1.0.2: resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM= -psl@^1.1.24: - version "1.8.0" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24" - integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ== - psl@^1.1.28: version "1.4.0" resolved "https://registry.yarnpkg.com/psl/-/psl-1.4.0.tgz#5dd26156cdb69fa1fdb8ab1991667d3f80ced7c2" @@ -22485,7 +22290,7 @@ punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1: resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -punycode@^1.2.4, punycode@^1.4.1: +punycode@^1.2.4: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= @@ -22537,11 +22342,6 @@ qs@6.7.0, qs@^6.4.0, qs@^6.5.1, qs@^6.6.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc" integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ== -qs@~6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" - integrity sha1-E+JtKK1rD/qpExLNO/cI7TUecjM= - qs@~6.5.2: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" @@ -24247,61 +24047,7 @@ request-promise@^4.2.2: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@2.81.0: - version "2.81.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" - integrity sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA= - dependencies: - aws-sign2 "~0.6.0" - aws4 "^1.2.1" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.0" - forever-agent "~0.6.1" - form-data "~2.1.1" - har-validator "~4.2.1" - hawk "~3.1.3" - http-signature "~1.1.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.7" - oauth-sign "~0.8.1" - performance-now "^0.2.0" - qs "~6.4.0" - safe-buffer "^5.0.1" - stringstream "~0.0.4" - tough-cookie "~2.3.0" - tunnel-agent "^0.6.0" - uuid "^3.0.0" - -request@2.88.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - integrity sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg== - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - -request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: +request@2.81.0, request@2.88.0, request@^2.74.0, request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== @@ -24792,7 +24538,7 @@ safefs@^4.1.0: editions "^1.1.1" graceful-fs "^4.1.4" -"safer-buffer@>= 2.1.2 < 3", "safer-buffer@>= 2.1.2 < 3.0.0", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== @@ -24897,12 +24643,14 @@ scheduler@^0.18.0: loose-envify "^1.1.0" object-assign "^4.1.1" -schema-utils@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.3.0.tgz#f5877222ce3e931edae039f17eb3716e7137f8cf" - integrity sha1-9YdyIs4+kx7a4DnxfrNxbnE3+M8= +schema-utils@1.0.0, schema-utils@^0.3.0, schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== dependencies: - ajv "^5.0.0" + ajv "^6.1.0" + ajv-errors "^1.0.0" + ajv-keywords "^3.1.0" schema-utils@^0.4.5: version "0.4.7" @@ -24912,15 +24660,6 @@ schema-utils@^0.4.5: ajv "^6.1.0" ajv-keywords "^3.1.0" -schema-utils@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" - integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== - dependencies: - ajv "^6.1.0" - ajv-errors "^1.0.0" - ajv-keywords "^3.1.0" - schema-utils@^2.0.0, schema-utils@^2.0.1, schema-utils@^2.5.0, schema-utils@^2.6.1, schema-utils@^2.6.5, schema-utils@^2.6.6, schema-utils@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.0.tgz#17151f76d8eae67fbbf77960c33c676ad9f4efc7" @@ -25420,13 +25159,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^2.0.0" -sntp@1.x.x: - version "1.0.9" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" - integrity sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg= - dependencies: - hoek "2.x.x" - socket-location@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/socket-location/-/socket-location-1.0.0.tgz#6f0c6f891c9a61c9a750265c14921d12196d266f" @@ -26130,11 +25862,6 @@ stringify-object@^3.2.1: is-obj "^1.0.1" is-regexp "^1.0.0" -stringstream@~0.0.4: - version "0.0.6" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.6.tgz#7880225b0d4ad10e30927d167a1d6f2fd3b33a72" - integrity sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA== - strip-ansi@*, strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" @@ -27182,21 +26909,6 @@ tough-cookie@^3.0.1: psl "^1.1.28" punycode "^2.1.1" -tough-cookie@~2.3.0: - version "2.3.4" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" - integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== - dependencies: - punycode "^1.4.1" - -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - integrity sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ== - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - tr46@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09" @@ -27530,21 +27242,11 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.0.2: +typescript@4.0.2, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.7.2: version "4.0.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.0.2.tgz#7ea7c88777c723c681e33bf7988be5d008d05ac2" integrity sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ== -typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5: - version "3.9.7" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.7.tgz#98d600a5ebdc38f40cb277522f12dc800e9e25fa" - integrity sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw== - -typescript@~3.7.2: - version "3.7.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" - integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== - ua-parser-js@^0.7.18: version "0.7.22" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3" From d1906c16d4b8f96f8d866c6c6d2b08ef3c50882f Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Mon, 2 Nov 2020 14:05:36 +0000 Subject: [PATCH 06/15] Fixed test failures --- .../api_keys_page/create_api_key_flyout.tsx | 2 +- .../management/api_keys/api_keys_api_client.mock.ts | 2 +- x-pack/plugins/security/server/routes/views/index.test.ts | 8 ++++++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx index 994e6d9a2b73c2..69dd188824a129 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx @@ -146,7 +146,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ doc="security-api-create-api-key.html#security-api-create-api-key-request-body" > diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts index 46551cae2bdc46..fac8f2e1b5b24a 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.mock.ts @@ -8,7 +8,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { APIKeysAPIClient } from './api_keys_api_client'; export const apiKeysAPIClientMock = { - create: (): PublicMethodsOf => ({ + create: (): jest.Mocked> => ({ checkPrivileges: jest.fn(), getApiKeys: jest.fn(), invalidateApiKeys: jest.fn(), diff --git a/x-pack/plugins/security/server/routes/views/index.test.ts b/x-pack/plugins/security/server/routes/views/index.test.ts index fa2088a80b1833..1be0a8e11a0fe8 100644 --- a/x-pack/plugins/security/server/routes/views/index.test.ts +++ b/x-pack/plugins/security/server/routes/views/index.test.ts @@ -22,6 +22,8 @@ describe('View routes', () => { Array [ "/security/access_agreement", "/security/account", + "/security/account/api-keys", + "/security/account/api-keys/create", "/security/logged_out", "/logout", "/security/overwritten_session", @@ -49,6 +51,8 @@ describe('View routes', () => { "/login", "/security/access_agreement", "/security/account", + "/security/account/api-keys", + "/security/account/api-keys/create", "/security/logged_out", "/logout", "/security/overwritten_session", @@ -77,6 +81,8 @@ describe('View routes', () => { "/login", "/security/access_agreement", "/security/account", + "/security/account/api-keys", + "/security/account/api-keys/create", "/security/logged_out", "/logout", "/security/overwritten_session", @@ -105,6 +111,8 @@ describe('View routes', () => { "/login", "/security/access_agreement", "/security/account", + "/security/account/api-keys", + "/security/account/api-keys/create", "/security/logged_out", "/logout", "/security/overwritten_session", From e5e2d766dfa9fcdeff4095c7fa1c06d8207e0f21 Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Mon, 2 Nov 2020 16:26:52 +0000 Subject: [PATCH 07/15] Fixed unit test and prettier errors --- .../api_keys_page/api_keys_table.tsx | 4 +- .../nav_control/nav_control_service.test.ts | 48 ++++++++++--------- 2 files changed, 28 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx index 3a6fa8e70daf32..324f5d1e4d241a 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_table.tsx @@ -73,8 +73,8 @@ export const ApiKeysTable: FunctionComponent = ({ {...props} items={props.loading && !createdItemId ? [] : props.items} tableCaption={i18n.translate('xpack.security.accountManagement.apiKeys.tableCaption', { - defaultMessage: 'Below is a table of your API Keys.', - })} + defaultMessage: 'Below is a table of your API Keys.', + })} message={ props.loading ? i18n.translate('xpack.security.accountManagement.apiKeys.loadingMessage', { diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index acf62f3376b8b1..c7c64ccc67f4ce 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -56,34 +56,38 @@ describe('SecurityNavControlService', () => { expect(target).toMatchInlineSnapshot(`
- + +
+ +
From 325a21cbc3fc0775ddf5e139fdae10074c4f115b Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Thu, 5 Nov 2020 19:52:12 +0000 Subject: [PATCH 08/15] Added suggestions from code review --- .../account_management_app.tsx | 2 +- .../api_keys_page/api_keys_page.tsx | 3 +- .../api_keys_page/create_api_key_flyout.tsx | 12 ++- .../components/form_flyout.tsx | 20 ++-- .../account_management/components/use_form.ts | 2 +- .../server/routes/api_keys/create.test.ts | 99 +++++++++++++++++++ .../security/server/routes/api_keys/create.ts | 11 ++- 7 files changed, 130 insertions(+), 19 deletions(-) create mode 100644 x-pack/plugins/security/server/routes/api_keys/create.test.ts diff --git a/x-pack/plugins/security/public/account_management/account_management_app.tsx b/x-pack/plugins/security/public/account_management/account_management_app.tsx index cf7ce75938e0ed..e4bab6bddf3224 100644 --- a/x-pack/plugins/security/public/account_management/account_management_app.tsx +++ b/x-pack/plugins/security/public/account_management/account_management_app.tsx @@ -8,7 +8,7 @@ import React, { lazy, Suspense, FunctionComponent } from 'react'; import ReactDOM from 'react-dom'; import { Router, useHistory } from 'react-router-dom'; import { History } from 'history'; -import { useAsync } from 'react-use'; +import useAsync from 'react-use/lib/useAsync'; import { i18n } from '@kbn/i18n'; import { EuiAvatar, diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx index 7f70ab228cb0f9..81ebf21ba97db8 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/api_keys_page.tsx @@ -6,7 +6,8 @@ import React, { FunctionComponent, useState } from 'react'; import { Route, useHistory } from 'react-router-dom'; -import { useAsyncFn, useMount } from 'react-use'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; +import useMount from 'react-use/lib/useMount'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx index 69dd188824a129..0df5788777e688 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useRef, FunctionComponent } from 'react'; +import React, { FunctionComponent } from 'react'; import { EuiCallOut, EuiFieldNumber, @@ -59,7 +59,6 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ }, [services.http] ); - const nameInput = useRef(null); return ( = ({ } )} isLoading={form.isSubmitting} - initialFocus={nameInput} onSubmit={eventHandlers.onSubmit} onClose={onClose} size="s" @@ -113,7 +111,6 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ name="name" defaultValue={form.values.name} isInvalid={!!form.errors.name} - inputRef={nameInput} fullWidth /> @@ -209,7 +206,12 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ isInvalid={!!form.errors.expiration} > { title: string; isLoading?: EuiButtonProps['isLoading']; - initialFocus?: RefObject; onSubmit: MouseEventHandler; submitButtonText: string; submitButtonColor?: EuiButtonProps['color']; @@ -37,14 +36,15 @@ export const FormFlyout: FunctionComponent = ({ onSubmit, isLoading, children, - initialFocus, ...rest }) => { + const submitButton = useRef(null); + useEffect(() => { - if (initialFocus && initialFocus.current) { - initialFocus.current.focus(); + if (submitButton.current) { + submitButton.current.focus(); } - }, [initialFocus]); + }, []); const flyout = ( @@ -65,7 +65,13 @@ export const FormFlyout: FunctionComponent = ({
- + {submitButtonText} diff --git a/x-pack/plugins/security/public/account_management/components/use_form.ts b/x-pack/plugins/security/public/account_management/components/use_form.ts index 80021b3a03d16f..1b9cadfba46809 100644 --- a/x-pack/plugins/security/public/account_management/components/use_form.ts +++ b/x-pack/plugins/security/public/account_management/components/use_form.ts @@ -12,7 +12,7 @@ import { ChangeEventHandler, ReactEventHandler, } from 'react'; -import { useAsyncFn } from 'react-use'; +import useAsyncFn from 'react-use/lib/useAsyncFn'; export interface FormOptions extends SubmitHandlerOptions { validate: ValidateFunction; diff --git a/x-pack/plugins/security/server/routes/api_keys/create.test.ts b/x-pack/plugins/security/server/routes/api_keys/create.test.ts new file mode 100644 index 00000000000000..d72d40f93bbb6d --- /dev/null +++ b/x-pack/plugins/security/server/routes/api_keys/create.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { kibanaResponseFactory } from '../../../../../../src/core/server'; +import { httpServerMock } from '../../../../../../src/core/server/mocks'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { routeDefinitionParamsMock } from '../index.mock'; +import { defineCreateApiKeyRoutes } from './create'; + +jest.mock('../licensed_route_handler'); + +const createLicensedRouteHandlerMock = createLicensedRouteHandler as jest.Mock; +createLicensedRouteHandlerMock.mockImplementation((handler) => handler); + +describe('defineCreateApiKeyRoutes', () => { + beforeEach(() => { + createLicensedRouteHandlerMock.mockClear(); + }); + + test('creates licensed route handler', () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + defineCreateApiKeyRoutes(mockRouteDefinitionParams); + expect(createLicensedRouteHandlerMock).toHaveBeenCalledTimes(1); + }); + + test('validates request body', () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + defineCreateApiKeyRoutes(mockRouteDefinitionParams); + const [[route]] = mockRouteDefinitionParams.router.post.mock.calls; + expect(() => (route.validate as any).body.validate({})).toThrow( + '[name]: expected value of type [string] but got [undefined]' + ); + }); + + test('creates API key', async () => { + const createAPIKeyResult = { + id: 'ID', + name: 'NAME', + api_key: 'API_KEY', + }; + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.authc.createAPIKey.mockResolvedValue(createAPIKeyResult); + defineCreateApiKeyRoutes(mockRouteDefinitionParams); + const [[, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'post', + path: '/internal/security/api_key', + body: {}, + }); + const response = await handler(undefined, mockRequest, kibanaResponseFactory); + expect(mockRouteDefinitionParams.authc.createAPIKey).toHaveBeenCalledWith( + mockRequest, + mockRequest.body + ); + expect(response.status).toBe(200); + expect(response.payload).toEqual(createAPIKeyResult); + }); + + test('returns bad request if api keys are not available', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.authc.createAPIKey.mockResolvedValue(undefined); + defineCreateApiKeyRoutes(mockRouteDefinitionParams); + const [[, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + + const response = await handler( + undefined, + httpServerMock.createKibanaRequest({ + method: 'post', + path: '/internal/security/api_key', + body: {}, + }), + kibanaResponseFactory + ); + expect(response.status).toBe(400); + expect(response.payload).toEqual({ message: 'API Keys are not available' }); + }); + + test('returns bad request if api key could not be created', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.authc.createAPIKey.mockRejectedValue(new Error('Error')); + defineCreateApiKeyRoutes(mockRouteDefinitionParams); + const [[, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + + const response = await handler( + undefined, + httpServerMock.createKibanaRequest({ + method: 'post', + path: '/internal/security/api_key', + body: {}, + }), + kibanaResponseFactory + ); + expect(response.status).toBe(500); + }); +}); diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts index 81b4ac2cbf505c..2bb6c077d16c29 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.ts @@ -7,7 +7,6 @@ import { schema } from '@kbn/config-schema'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { wrapIntoCustomErrorResponse } from '../../errors'; -import { elasticsearchRoleSchema } from '../authorization/roles/model/put_payload'; import { RouteDefinitionParams } from '..'; export function defineCreateApiKeyRoutes({ router, authc }: RouteDefinitionParams) { @@ -18,9 +17,13 @@ export function defineCreateApiKeyRoutes({ router, authc }: RouteDefinitionParam body: schema.object({ name: schema.string(), expiration: schema.maybe(schema.string()), - role_descriptors: schema.recordOf(schema.string(), elasticsearchRoleSchema, { - defaultValue: {}, - }), + role_descriptors: schema.recordOf( + schema.string(), + schema.object({}, { unknowns: 'allow' }), + { + defaultValue: {}, + } + ), }), }, }, From ef4b6e4179c5a399e2d4ca2280f6b97d79d90791 Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Fri, 6 Nov 2020 09:16:10 +0000 Subject: [PATCH 09/15] Fixed types in tests --- .../security/server/routes/api_keys/create.test.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/security/server/routes/api_keys/create.test.ts b/x-pack/plugins/security/server/routes/api_keys/create.test.ts index d72d40f93bbb6d..6f4c2a9b85c48b 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { kibanaResponseFactory } from '../../../../../../src/core/server'; +import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; import { httpServerMock } from '../../../../../../src/core/server/mocks'; import { createLicensedRouteHandler } from '../licensed_route_handler'; import { routeDefinitionParamsMock } from '../index.mock'; @@ -15,6 +15,8 @@ jest.mock('../licensed_route_handler'); const createLicensedRouteHandlerMock = createLicensedRouteHandler as jest.Mock; createLicensedRouteHandlerMock.mockImplementation((handler) => handler); +const contextMock = ({} as unknown) as RequestHandlerContext; + describe('defineCreateApiKeyRoutes', () => { beforeEach(() => { createLicensedRouteHandlerMock.mockClear(); @@ -51,7 +53,7 @@ describe('defineCreateApiKeyRoutes', () => { path: '/internal/security/api_key', body: {}, }); - const response = await handler(undefined, mockRequest, kibanaResponseFactory); + const response = await handler(contextMock, mockRequest, kibanaResponseFactory); expect(mockRouteDefinitionParams.authc.createAPIKey).toHaveBeenCalledWith( mockRequest, mockRequest.body @@ -62,12 +64,12 @@ describe('defineCreateApiKeyRoutes', () => { test('returns bad request if api keys are not available', async () => { const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - mockRouteDefinitionParams.authc.createAPIKey.mockResolvedValue(undefined); + mockRouteDefinitionParams.authc.createAPIKey.mockResolvedValue(null); defineCreateApiKeyRoutes(mockRouteDefinitionParams); const [[, handler]] = mockRouteDefinitionParams.router.post.mock.calls; const response = await handler( - undefined, + contextMock, httpServerMock.createKibanaRequest({ method: 'post', path: '/internal/security/api_key', @@ -86,7 +88,7 @@ describe('defineCreateApiKeyRoutes', () => { const [[, handler]] = mockRouteDefinitionParams.router.post.mock.calls; const response = await handler( - undefined, + contextMock, httpServerMock.createKibanaRequest({ method: 'post', path: '/internal/security/api_key', From 6dd9fa14ee687a992c4372d76143213b14989f96 Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Fri, 6 Nov 2020 11:30:38 +0000 Subject: [PATCH 10/15] Fix test failure --- .../__tests__/step_screenshot_display.test.tsx | 14 ++++++++------ .../monitor/synthetics/step_screenshot_display.tsx | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx index 16db430dbd73a1..e4ab6c64461606 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/__tests__/step_screenshot_display.test.tsx @@ -6,15 +6,17 @@ import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; -import * as reactUse from 'react-use'; +import useIntersection from 'react-use/lib/useIntersection'; import { StepScreenshotDisplay } from '../step_screenshot_display'; -describe('StepScreenshotDisplayProps', () => { - // @ts-ignore missing fields don't matter in this test, the component in question only relies on `isIntersecting` - jest.spyOn(reactUse, 'useIntersection').mockImplementation(() => ({ - isIntersecting: true, - })); +jest.mock('react-use/lib/useIntersection'); + +const useIntersectionMock = useIntersection as jest.Mock; +useIntersectionMock.mockImplementation(() => ({ + isIntersecting: true, +})); +describe('StepScreenshotDisplayProps', () => { it('displays screenshot thumbnail when present', () => { const wrapper = mountWithIntl( Date: Mon, 9 Nov 2020 12:29:54 +0000 Subject: [PATCH 11/15] Added e2e tests --- .../api_keys_page/create_api_key_flyout.tsx | 5 +- .../components/form_flyout.tsx | 20 +++----- .../account_management.config.ts | 51 +++++++++++++++++++ .../tests/account_management/api_keys_page.ts | 43 ++++++++++++++++ .../tests/account_management/index.ts | 15 ++++++ 5 files changed, 120 insertions(+), 14 deletions(-) create mode 100644 x-pack/test/security_functional/account_management.config.ts create mode 100644 x-pack/test/security_functional/tests/account_management/api_keys_page.ts create mode 100644 x-pack/test/security_functional/tests/account_management/index.ts diff --git a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx index 0df5788777e688..d876ea75c4282a 100644 --- a/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx +++ b/x-pack/plugins/security/public/account_management/api_keys_page/create_api_key_flyout.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FunctionComponent } from 'react'; +import React, { useRef, FunctionComponent } from 'react'; import { EuiCallOut, EuiFieldNumber, @@ -59,6 +59,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ }, [services.http] ); + const nameInput = useRef(null); return ( = ({ } )} isLoading={form.isSubmitting} + initialFocus={nameInput} onSubmit={eventHandlers.onSubmit} onClose={onClose} size="s" @@ -111,6 +113,7 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ name="name" defaultValue={form.values.name} isInvalid={!!form.errors.name} + inputRef={nameInput} fullWidth /> diff --git a/x-pack/plugins/security/public/account_management/components/form_flyout.tsx b/x-pack/plugins/security/public/account_management/components/form_flyout.tsx index 6da8e7b638faac..63be4381285670 100644 --- a/x-pack/plugins/security/public/account_management/components/form_flyout.tsx +++ b/x-pack/plugins/security/public/account_management/components/form_flyout.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useRef, FunctionComponent, MouseEventHandler } from 'react'; +import React, { useEffect, useRef, FunctionComponent, MouseEventHandler, RefObject } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, @@ -24,6 +24,7 @@ import { export interface FormFlyoutProps extends Omit { title: string; isLoading?: EuiButtonProps['isLoading']; + initialFocus?: RefObject; onSubmit: MouseEventHandler; submitButtonText: string; submitButtonColor?: EuiButtonProps['color']; @@ -36,15 +37,14 @@ export const FormFlyout: FunctionComponent = ({ onSubmit, isLoading, children, + initialFocus, ...rest }) => { - const submitButton = useRef(null); - useEffect(() => { - if (submitButton.current) { - submitButton.current.focus(); + if (initialFocus && initialFocus.current) { + initialFocus.current.focus(); } - }, []); + }, [initialFocus]); const flyout = ( @@ -65,13 +65,7 @@ export const FormFlyout: FunctionComponent = ({
- + {submitButtonText} diff --git a/x-pack/test/security_functional/account_management.config.ts b/x-pack/test/security_functional/account_management.config.ts new file mode 100644 index 00000000000000..9c336fc0b833b2 --- /dev/null +++ b/x-pack/test/security_functional/account_management.config.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve } from 'path'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services } from '../functional/services'; +import { pageObjects } from '../functional/page_objects'; + +// the default export of config files must be a config provider +// that returns an object with the projects config values +export default async function ({ readConfigFile }: FtrConfigProviderContext) { + const kibanaCommonConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const kibanaFunctionalConfig = await readConfigFile( + require.resolve('../../../test/functional/config.js') + ); + + return { + testFiles: [resolve(__dirname, './tests/account_management')], + services, + pageObjects, + servers: kibanaFunctionalConfig.get('servers'), + esTestCluster: { + ...kibanaCommonConfig.get('esTestCluster'), + license: 'trial', + serverArgs: [ + ...kibanaCommonConfig.get('esTestCluster.serverArgs'), + 'xpack.security.authc.api_key.enabled=true', + ], + }, + kbnTestServer: kibanaCommonConfig.get('kbnTestServer'), + apps: { + ...kibanaFunctionalConfig.get('apps'), + accountManagement: { + pathname: '/security/account', + }, + accountManagementApiKeys: { + pathname: '/security/account/api-keys', + }, + }, + esArchiver: { directory: resolve(__dirname, 'es_archives') }, + screenshots: { directory: resolve(__dirname, 'screenshots') }, + junit: { + reportName: 'Chrome X-Pack Security Functional Tests (Account Management)', + }, + }; +} diff --git a/x-pack/test/security_functional/tests/account_management/api_keys_page.ts b/x-pack/test/security_functional/tests/account_management/api_keys_page.ts new file mode 100644 index 00000000000000..90f1e3fc222853 --- /dev/null +++ b/x-pack/test/security_functional/tests/account_management/api_keys_page.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const pageObjects = getPageObjects(['common']); + const log = getService('log'); + const find = getService('find'); + const security = getService('security'); + + describe('API keys page', function () { + before(async () => { + await security.role.create('allow_manage_own_api_key_role', { + elasticsearch: { + cluster: ['manage_own_api_key'], + }, + }); + await security.testUser.setRoles(['allow_manage_own_api_key_role']); + await pageObjects.common.navigateToApp('accountManagementApiKeys'); + }); + + after(async () => { + await security.testUser.restoreDefaults(); + }); + + it('creates API key', async () => { + log.debug('Checking for section header'); + + await find.clickByLinkText('Create API key'); + + const nameInput = await find.byName('name'); + await nameInput.type('test'); + + await find.clickByButtonText('Create API key'); + + await find.byCssSelector('.euiCallOut--success'); + }); + }); +} diff --git a/x-pack/test/security_functional/tests/account_management/index.ts b/x-pack/test/security_functional/tests/account_management/index.ts new file mode 100644 index 00000000000000..a39109faf6b89c --- /dev/null +++ b/x-pack/test/security_functional/tests/account_management/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ loadTestFile }: FtrProviderContext) { + describe('security app - Account Management', function () { + this.tags('ciGroup4'); + + loadTestFile(require.resolve('./api_keys_page')); + }); +} From dad4664a6da6bd7452c1db8a5c3090de8a0a812f Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Mon, 9 Nov 2020 15:53:39 +0000 Subject: [PATCH 12/15] Fix linting errors --- .../public/account_management/components/form_flyout.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x-pack/plugins/security/public/account_management/components/form_flyout.tsx b/x-pack/plugins/security/public/account_management/components/form_flyout.tsx index 63be4381285670..92d282bc972ccf 100644 --- a/x-pack/plugins/security/public/account_management/components/form_flyout.tsx +++ b/x-pack/plugins/security/public/account_management/components/form_flyout.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useRef, FunctionComponent, MouseEventHandler, RefObject } from 'react'; +import React, { useEffect, FunctionComponent, MouseEventHandler, RefObject } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiTitle, From aeb06623e4c5b6cd67d8b18706c6ad6f052796db Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Tue, 10 Nov 2020 09:12:02 +0000 Subject: [PATCH 13/15] Added link to account management --- .../empty_prompt/empty_prompt.tsx | 18 ++++-------------- .../test/functional/apps/api_keys/home_page.ts | 4 ++-- .../functional/page_objects/api_keys_page.ts | 4 ++-- 3 files changed, 8 insertions(+), 18 deletions(-) diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx index 9b2ccfcb99ef30..17e2ed1406c67e 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx @@ -44,17 +44,7 @@ export const EmptyPrompt: React.FunctionComponent = ({

- - - ), - }} + defaultMessage="You can create an API key from your account." />

@@ -62,12 +52,12 @@ export const EmptyPrompt: React.FunctionComponent = ({ actions={ navigateToApp('dev_tools')} - data-test-subj="goToConsoleButton" + onClick={() => navigateToApp('security_account', { path: 'api-keys' })} + data-test-subj="goToAccountButton" > } diff --git a/x-pack/test/functional/apps/api_keys/home_page.ts b/x-pack/test/functional/apps/api_keys/home_page.ts index 39d8449218ffad..52355aba3a5c7b 100644 --- a/x-pack/test/functional/apps/api_keys/home_page.ts +++ b/x-pack/test/functional/apps/api_keys/home_page.ts @@ -34,8 +34,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const headers = await testSubjects.findAll('noApiKeysHeader'); if (headers.length > 0) { expect(await headers[0].getVisibleText()).to.be('No API keys'); - const goToConsoleButton = await pageObjects.apiKeys.getGoToConsoleButton(); - expect(await goToConsoleButton.isDisplayed()).to.be(true); + const goToAccountButton = await pageObjects.apiKeys.getGoToAccountButton(); + expect(await goToAccountButton.isDisplayed()).to.be(true); } else { // page may already contain EiTable with data, then check API Key Admin text const description = await pageObjects.apiKeys.getApiKeyAdminDesc(); diff --git a/x-pack/test/functional/page_objects/api_keys_page.ts b/x-pack/test/functional/page_objects/api_keys_page.ts index fa10c5a574c095..d1aeda653f8a0b 100644 --- a/x-pack/test/functional/page_objects/api_keys_page.ts +++ b/x-pack/test/functional/page_objects/api_keys_page.ts @@ -18,8 +18,8 @@ export function ApiKeysPageProvider({ getService }: FtrProviderContext) { return await testSubjects.getVisibleText('apiKeyAdminDescriptionCallOut'); }, - async getGoToConsoleButton() { - return await testSubjects.find('goToConsoleButton'); + async getGoToAccountButton() { + return await testSubjects.find('goToAccountButton'); }, async apiKeysPermissionDeniedMessage() { From fe64fd99d0c8186d7d22c5e94e695ceb8261160b Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Tue, 10 Nov 2020 10:22:23 +0000 Subject: [PATCH 14/15] Fixed translations --- .../api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx | 4 ++-- x-pack/plugins/translations/translations/ja-JP.json | 3 --- x-pack/plugins/translations/translations/zh-CN.json | 3 --- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx index 17e2ed1406c67e..330637087d9d8c 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/empty_prompt/empty_prompt.tsx @@ -7,7 +7,7 @@ import React, { Fragment } from 'react'; import { ApplicationStart } from 'kibana/public'; -import { EuiEmptyPrompt, EuiButton, EuiLink } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { DocumentationLinksService } from '../../documentation_links'; @@ -56,7 +56,7 @@ export const EmptyPrompt: React.FunctionComponent = ({ data-test-subj="goToAccountButton" >
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 485b24dced3463..f9404d7882e953 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -15917,9 +15917,6 @@ "xpack.security.management.apiKeys.table.apiKeysTitle": "API キー", "xpack.security.management.apiKeys.table.creationDateColumnName": "作成済み", "xpack.security.management.apiKeys.table.emptyPromptAdminTitle": "API キーがありません", - "xpack.security.management.apiKeys.table.emptyPromptConsoleButtonMessage": "コンソールに移動してください", - "xpack.security.management.apiKeys.table.emptyPromptDescription": "コンソールで {link} を作成できます。", - "xpack.security.management.apiKeys.table.emptyPromptDocsLinkMessage": "API キー", "xpack.security.management.apiKeys.table.emptyPromptNonAdminTitle": "まだ API キーがありません", "xpack.security.management.apiKeys.table.expirationDateColumnName": "有効期限", "xpack.security.management.apiKeys.table.expirationDateNeverMessage": "無し", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 98d13011d3306f..58d2d048d76cdf 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -15935,9 +15935,6 @@ "xpack.security.management.apiKeys.table.apiKeysTitle": "API 密钥", "xpack.security.management.apiKeys.table.creationDateColumnName": "创建时间", "xpack.security.management.apiKeys.table.emptyPromptAdminTitle": "无 API 密钥", - "xpack.security.management.apiKeys.table.emptyPromptConsoleButtonMessage": "前往 Console", - "xpack.security.management.apiKeys.table.emptyPromptDescription": "您可以从 Console 创建 {link}。", - "xpack.security.management.apiKeys.table.emptyPromptDocsLinkMessage": "API 密钥", "xpack.security.management.apiKeys.table.emptyPromptNonAdminTitle": "您未有任何 API 密钥", "xpack.security.management.apiKeys.table.expirationDateColumnName": "过期", "xpack.security.management.apiKeys.table.expirationDateNeverMessage": "永远不", From 16848fb39fdaa67aea9a5ee03f218c5e751e67b2 Mon Sep 17 00:00:00 2001 From: Thom Heymann Date: Wed, 11 Nov 2020 09:56:37 +0000 Subject: [PATCH 15/15] Remove unused eslint disable --- .../security/public/nav_control/nav_control_component.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx index ff676313832f30..ee90eb1379d0ab 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_component.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_component.tsx @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -/* eslint-disable @elastic/eui/href-or-on-click */ - import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component } from 'react';