diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap index d48e34b2e48370..f611ec978b6b36 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/__snapshots__/dashboard_empty_screen.test.tsx.snap @@ -2,6 +2,31 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` - -
- -
+ - - -
+ - - - - - - -
- - -
-

- This dashboard is empty. Let’s fill it up! -

-
-
- -
- - -
-

- Click the - - - - button in the menu bar above to add a visualization to the dashboard. -

-
-
- -
- -

- - - -

-
- - -
-
-
-
+ Add an existing + + + +   + + or new object to this dashboard +

+ + + + + +
+ +

+ + + +

+
@@ -373,6 +361,31 @@ exports[`DashboardEmptyScreen renders correctly with visualize paragraph 1`] = ` exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] = `
@@ -581,59 +630,48 @@ exports[`DashboardEmptyScreen renders correctly without visualize paragraph 1`] className="euiPageBody" >
- - - - - - -
- + +
-

This dashboard is empty. Let’s fill it up! -

+

- -
-

- Click the - + - - - button in the menu bar above to start working on your new dashboard. -

-
-
+

+ Click + +   + + + + + +   + + in the menu bar above to start adding panels. +

+
+ +
+ +
diff --git a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx index 1c450879ee5530..381ced2efd8e3e 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/__tests__/dashboard_empty_screen.test.tsx @@ -24,11 +24,16 @@ import { } from '../np_ready/dashboard_empty_screen'; // @ts-ignore import { findTestSubject } from '@elastic/eui/lib/test'; +import { coreMock } from '../../../../../../core/public/mocks'; describe('DashboardEmptyScreen', () => { + const setupMock = coreMock.createSetup(); + const defaultProps = { showLinkToVisualize: true, onLinkClick: jest.fn(), + uiSettings: setupMock.uiSettings, + http: setupMock.http, }; function mountComponent(props?: DashboardEmptyScreenProps) { diff --git a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss index d9eadf6c0e37d0..03a8a07d6b17da 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss +++ b/src/legacy/core_plugins/kibana/public/dashboard/_dashboard_app.scss @@ -6,5 +6,31 @@ .dshStartScreen { text-align: center; - padding: $euiSizeS; +} + +.dshStartScreen__pageContent { + padding: $euiSizeXXL; +} + +.dshStartScreen__panelDesc { + max-width: 260px; + margin: 0 auto; +} + +.dshEmptyWidget { + border: $euiBorderThin; + border-style: dashed; + border-radius: $euiBorderRadius; + padding: $euiSizeXXL * 2; + max-width: 400px; + margin-left: $euiSizeS; + text-align: center; +} + +.dshEmptyWidget { + border: 2px dashed $euiColorLightShade; + padding: 4 * $euiSize; + max-width: 20em; + margin-left: 10px; + text-align: center; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx index 08637174c8cec9..8fcc7e4c263210 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_app_controller.tsx @@ -123,7 +123,7 @@ export class DashboardAppController { timefilter: { timefilter }, }, }, - core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects }, + core: { notifications, overlays, chrome, injectedMetadata, uiSettings, savedObjects, http }, }: DashboardAppControllerDependencies) { new FilterStateManager(globalState, getAppState, filterManager); const queryFilter = filterManager; @@ -197,6 +197,8 @@ export class DashboardAppController { const emptyScreenProps: DashboardEmptyScreenProps = { onLinkClick: shouldShowEditHelp ? $scope.showAddPanel : $scope.enterEditMode, showLinkToVisualize: shouldShowEditHelp, + uiSettings, + http, }; if (shouldShowEditHelp) { emptyScreenProps.onVisualizeClick = addVisualization; diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx index 2fc78d64d0a0cd..ae5319c560ab95 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen.tsx @@ -19,94 +19,110 @@ import React from 'react'; import { I18nProvider } from '@kbn/i18n/react'; import { - EuiIcon, EuiLink, EuiSpacer, EuiPageContent, EuiPageBody, EuiPage, + EuiImage, EuiText, EuiButton, } from '@elastic/eui'; +import { IUiSettingsClient, HttpStart } from 'kibana/public'; import * as constants from './dashboard_empty_screen_constants'; export interface DashboardEmptyScreenProps { showLinkToVisualize: boolean; onLinkClick: () => void; onVisualizeClick?: () => void; + uiSettings: IUiSettingsClient; + http: HttpStart; } export function DashboardEmptyScreen({ showLinkToVisualize, onLinkClick, onVisualizeClick, + uiSettings, + http, }: DashboardEmptyScreenProps) { + const IS_DARK_THEME = uiSettings.get('theme:darkMode'); + const emptyStateGraphicURL = IS_DARK_THEME + ? '/plugins/kibana/home/assets/welcome_graphic_dark_2x.png' + : '/plugins/kibana/home/assets/welcome_graphic_light_2x.png'; const linkToVisualizeParagraph = (

{constants.createNewVisualizationButton}

); const paragraph = ( - description1: string, + description1: string | null, description2: string, linkText: string, ariaLabel: string, dataTestSubj?: string ) => { return ( - +

{description1} + {description1 &&  } {linkText} +   {description2}

); }; - const addVisualizationParagraph = ( - - {paragraph( - constants.addVisualizationDescription1, - constants.addVisualizationDescription2, - constants.addVisualizationLinkText, - constants.addVisualizationLinkAriaLabel, - 'emptyDashboardAddPanelButton' - )} - - {linkToVisualizeParagraph} - - ); const enterEditModeParagraph = paragraph( constants.howToStartWorkingOnNewDashboardDescription1, constants.howToStartWorkingOnNewDashboardDescription2, constants.howToStartWorkingOnNewDashboardEditLinkText, constants.howToStartWorkingOnNewDashboardEditLinkAriaLabel ); - return ( - - - - - - - -

{constants.fillDashboardTitle}

-
- - {showLinkToVisualize ? addVisualizationParagraph : enterEditModeParagraph} -
-
-
-
+ const enterViewModeParagraph = paragraph( + null, + constants.addNewVisualizationDescription, + constants.addExistingVisualizationLinkText, + constants.addExistingVisualizationLinkAriaLabel + ); + const viewMode = ( + + + + + +

{constants.fillDashboardTitle}

+
+ +
{enterEditModeParagraph}
+
+
+
+ ); + const editMode = ( +
+ {enterViewModeParagraph} + + {linkToVisualizeParagraph} +
); + return {showLinkToVisualize ? editMode : viewMode}; } diff --git a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx index 03004f6270fef9..513e6cb685a7ac 100644 --- a/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx +++ b/src/legacy/core_plugins/kibana/public/dashboard/np_ready/dashboard_empty_screen_constants.tsx @@ -19,40 +19,20 @@ import { i18n } from '@kbn/i18n'; -export const addVisualizationDescription1: string = i18n.translate( - 'kbn.dashboard.addVisualizationDescription1', - { - defaultMessage: 'Click the ', - } -); -export const addVisualizationDescription2: string = i18n.translate( - 'kbn.dashboard.addVisualizationDescription2', - { - defaultMessage: ' button in the menu bar above to add a visualization to the dashboard.', - } -); -export const addVisualizationLinkText: string = i18n.translate( - 'kbn.dashboard.addVisualizationLinkText', - { - defaultMessage: 'Add', - } -); -export const addVisualizationLinkAriaLabel: string = i18n.translate( - 'kbn.dashboard.addVisualizationLinkAriaLabel', - { - defaultMessage: 'Add visualization', - } -); +/** VIEW MODE CONSTANTS **/ +export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', { + defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!', +}); export const howToStartWorkingOnNewDashboardDescription1: string = i18n.translate( 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription1', { - defaultMessage: 'Click the ', + defaultMessage: 'Click', } ); export const howToStartWorkingOnNewDashboardDescription2: string = i18n.translate( 'kbn.dashboard.howToStartWorkingOnNewDashboardDescription2', { - defaultMessage: ' button in the menu bar above to start working on your new dashboard.', + defaultMessage: 'in the menu bar above to start adding panels.', } ); export const howToStartWorkingOnNewDashboardEditLinkText: string = i18n.translate( @@ -67,13 +47,23 @@ export const howToStartWorkingOnNewDashboardEditLinkAriaLabel: string = i18n.tra defaultMessage: 'Edit dashboard', } ); -export const fillDashboardTitle: string = i18n.translate('kbn.dashboard.fillDashboardTitle', { - defaultMessage: 'This dashboard is empty. Let\u2019s fill it up!', -}); -export const visualizeAppLinkTest: string = i18n.translate( - 'kbn.dashboard.visitVisualizeAppLinkText', +/** EDIT MODE CONSTANTS **/ +export const addExistingVisualizationLinkText: string = i18n.translate( + 'kbn.dashboard.addExistingVisualizationLinkText', + { + defaultMessage: 'Add an existing', + } +); +export const addExistingVisualizationLinkAriaLabel: string = i18n.translate( + 'kbn.dashboard.addVisualizationLinkAriaLabel', + { + defaultMessage: 'Add an existing visualization', + } +); +export const addNewVisualizationDescription: string = i18n.translate( + 'kbn.dashboard.addNewVisualizationText', { - defaultMessage: 'visit the Visualize app', + defaultMessage: 'or new object to this dashboard', } ); export const createNewVisualizationButton: string = i18n.translate( @@ -82,3 +72,9 @@ export const createNewVisualizationButton: string = i18n.translate( defaultMessage: 'Create new', } ); +export const createNewVisualizationButtonAriaLabel: string = i18n.translate( + 'kbn.dashboard.createNewVisualizationButtonAriaLabel', + { + defaultMessage: 'Create new visualization button', + } +); diff --git a/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png b/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png new file mode 100644 index 00000000000000..8f551c54bd5527 Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_dark_2x.png differ diff --git a/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_light_2x.png b/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_light_2x.png new file mode 100644 index 00000000000000..034e8b87ff0f5b Binary files /dev/null and b/src/legacy/core_plugins/kibana/public/home/assets/welcome_graphic_light_2x.png differ diff --git a/test/functional/apps/dashboard/empty_dashboard.js b/test/functional/apps/dashboard/empty_dashboard.js index d46daff183abf1..c91b7bd1ecee0d 100644 --- a/test/functional/apps/dashboard/empty_dashboard.js +++ b/test/functional/apps/dashboard/empty_dashboard.js @@ -44,13 +44,13 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); }); - it('should display add button', async () => { - const addButtonExists = await testSubjects.exists('emptyDashboardAddPanelButton'); - expect(addButtonExists).to.be(true); + it('should display empty widget', async () => { + const emptyWidgetExists = await testSubjects.exists('emptyDashboardWidget'); + expect(emptyWidgetExists).to.be(true); }); it.skip('should open add panel when add button is clicked', async () => { - await testSubjects.click('emptyDashboardAddPanelButton'); + await testSubjects.click('dashboardAddPanelButton'); const isAddPanelOpen = await dashboardAddPanel.isAddPanelOpen(); expect(isAddPanelOpen).to.be(true); }); diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index fc0252c86fe50b..33a6b716e9b8a1 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -15,6 +15,7 @@ import { } from 'src/core/server'; import { Alert, + PartialAlert, RawAlert, AlertTypeRegistry, AlertAction, @@ -69,28 +70,26 @@ export interface FindOptions { }; } -interface FindResult { +export interface FindResult { page: number; perPage: number; total: number; - data: object[]; + data: Alert[]; } interface CreateOptions { - data: Pick< + data: Omit< Alert, - Exclude< - keyof Alert, - | 'createdBy' - | 'updatedBy' - | 'createdAt' - | 'updatedAt' - | 'apiKey' - | 'apiKeyOwner' - | 'muteAll' - | 'mutedInstanceIds' - | 'actions' - > + | 'id' + | 'createdBy' + | 'updatedBy' + | 'createdAt' + | 'updatedAt' + | 'apiKey' + | 'apiKeyOwner' + | 'muteAll' + | 'mutedInstanceIds' + | 'actions' > & { actions: NormalizedAlertAction[] }; options?: { migrationVersion?: Record; @@ -146,7 +145,7 @@ export class AlertsClient { this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin; } - public async create({ data, options }: CreateOptions) { + public async create({ data, options }: CreateOptions): Promise { // Throws an error if alert type isn't registered const alertType = this.alertTypeRegistry.get(data.alertTypeId); const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); @@ -199,26 +198,29 @@ export class AlertsClient { ); } - public async get({ id }: { id: string }) { + public async get({ id }: { id: string }): Promise { const result = await this.savedObjectsClient.get('alert', id); return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); } public async find({ options = {} }: FindOptions = {}): Promise { - const results = await this.savedObjectsClient.find({ + const { + page, + per_page: perPage, + total, + saved_objects: data, + } = await this.savedObjectsClient.find({ ...options, type: 'alert', }); - const data = results.saved_objects.map(result => - this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references) - ); - return { - page: results.page, - perPage: results.per_page, - total: results.total, - data, + page, + perPage, + total, + data: data.map(({ id, attributes, updated_at, references }) => + this.getAlertFromRaw(id, attributes, updated_at, references) + ), }; } @@ -234,7 +236,7 @@ export class AlertsClient { return removeResult; } - public async update({ id, data }: UpdateOptions) { + public async update({ id, data }: UpdateOptions): Promise { const decryptedAlertSavedObject = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< RawAlert >('alert', id, { namespace: this.namespace }); @@ -257,7 +259,7 @@ export class AlertsClient { private async updateAlert( { id, data }: UpdateOptions, { attributes, version }: SavedObject - ) { + ): Promise { const alertType = this.alertTypeRegistry.get(attributes.alertTypeId); // Validate @@ -287,7 +289,7 @@ export class AlertsClient { await this.invalidateApiKey({ apiKey: attributes.apiKey }); - return this.getAlertFromRaw( + return this.getPartialAlertFromRaw( id, updatedObject.attributes, updatedObject.updated_at, @@ -494,24 +496,34 @@ export class AlertsClient { } private getAlertFromRaw( + id: string, + rawAlert: RawAlert, + updatedAt: SavedObject['updated_at'], + references: SavedObjectReference[] | undefined + ): Alert { + // In order to support the partial update API of Saved Objects we have to support + // partial updates of an Alert, but when we receive an actual RawAlert, it is safe + // to cast the result to an Alert + return this.getPartialAlertFromRaw(id, rawAlert, updatedAt, references) as Alert; + } + + private getPartialAlertFromRaw( id: string, rawAlert: Partial, updatedAt: SavedObject['updated_at'], references: SavedObjectReference[] | undefined - ) { - if (!rawAlert.actions) { - return { - id, - ...rawAlert, - }; - } - const actions = this.injectReferencesIntoActions(rawAlert.actions, references || []); + ): PartialAlert { return { id, ...rawAlert, + // we currently only support the Interval Schedule type + // Once we support additional types, this type signature will likely change + schedule: rawAlert.schedule as IntervalSchedule, updatedAt: updatedAt ? new Date(updatedAt) : new Date(rawAlert.createdAt!), createdAt: new Date(rawAlert.createdAt!), - actions, + actions: rawAlert.actions + ? this.injectReferencesIntoActions(rawAlert.actions, references || []) + : [], }; } diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index 03b33b0bd40b0d..2a0ae78fd78b22 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -20,6 +20,7 @@ const mockedAlert = { params: { bar: true, }, + throttle: '30s', actions: [ { group: 'default', @@ -44,6 +45,13 @@ test('creates an alert with proper parameters', async () => { const updatedAt = new Date(); alertsClient.create.mockResolvedValueOnce({ ...mockedAlert, + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKey: '', + apiKeyOwner: '', + mutedInstanceIds: [], createdAt, updatedAt, id: '123', @@ -71,8 +79,14 @@ test('creates an alert with proper parameters', async () => { }, ], "alertTypeId": "1", + "apiKey": "", + "apiKeyOwner": "", "consumer": "bar", + "createdBy": "", + "enabled": true, "id": "123", + "muteAll": false, + "mutedInstanceIds": Array [], "name": "abc", "params": Object { "bar": true, @@ -83,6 +97,8 @@ test('creates an alert with proper parameters', async () => { "tags": Array [ "foo", ], + "throttle": "30s", + "updatedBy": "", } `); expect(alertsClient.create).toHaveBeenCalledTimes(1); @@ -112,7 +128,7 @@ test('creates an alert with proper parameters', async () => { "tags": Array [ "foo", ], - "throttle": null, + "throttle": "30s", }, }, ] diff --git a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts index 5b1bdc7f697086..320e9042d87c59 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts @@ -29,6 +29,17 @@ const mockedAlert = { }, }, ], + consumer: 'bar', + name: 'abc', + tags: ['foo'], + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKey: '', + apiKeyOwner: '', + throttle: '30s', + mutedInstanceIds: [], }; beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index c12f8be3380cd9..9b03f9b02aa0af 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -65,6 +65,7 @@ export interface IntervalSchedule extends SavedObjectAttributes { } export interface Alert { + id: string; enabled: boolean; name: string; tags: string[]; @@ -85,6 +86,8 @@ export interface Alert { mutedInstanceIds: string[]; } +export type PartialAlert = Pick & Partial>; + export interface RawAlert extends SavedObjectAttributes { enabled: boolean; name: string; diff --git a/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts index 79ffe58d419bd3..b2cfd826e62079 100644 --- a/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/legacy/plugins/beats_management/public/lib/adapters/framework/kibana_framework_adapter.ts @@ -11,6 +11,8 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { UIRoutes } from 'ui/routes'; import { isLeft } from 'fp-ts/lib/Either'; +import { npSetup } from 'ui/new_platform'; +import { SecurityPluginSetup } from '../../../../../../../plugins/security/public'; import { BufferedKibanaServiceCall, KibanaAdapterServiceRefs, KibanaUIConfig } from '../../types'; import { FrameworkAdapter, @@ -58,7 +60,7 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { }; public async waitUntilFrameworkReady(): Promise { - const $injector = await this.onKibanaReady(); + await this.onKibanaReady(); const xpackInfo: any = this.xpackInfoService; let xpackInfoUnpacked: FrameworkInfo; @@ -95,8 +97,10 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter { } this.xpackInfo = xpackInfoUnpacked; + const securitySetup = ((npSetup.plugins as unknown) as { security?: SecurityPluginSetup }) + .security; try { - this.shieldUser = await $injector.get('ShieldUser').getCurrent().$promise; + this.shieldUser = (await securitySetup?.authc.getCurrentUser()) || null; const assertUser = RuntimeFrameworkUser.decode(this.shieldUser); if (isLeft(assertUser)) { diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js index aa7f88a62397cc..83446278fdeca8 100755 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js +++ b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js @@ -8,6 +8,7 @@ import React from 'react'; import { render } from 'react-dom'; import { isEmpty } from 'lodash'; import { uiModules } from 'ui/modules'; +import { npSetup } from 'ui/new_platform'; import { toastNotifications } from 'ui/notify'; import { I18nContext } from 'ui/i18n'; import { PipelineEditor } from '../../../../components/pipeline_editor'; @@ -21,7 +22,6 @@ app.directive('pipelineEdit', function($injector) { const pipelineService = $injector.get('pipelineService'); const licenseService = $injector.get('logstashLicenseService'); const kbnUrl = $injector.get('kbnUrl'); - const shieldUser = $injector.get('ShieldUser'); const $route = $injector.get('$route'); return { @@ -32,7 +32,7 @@ app.directive('pipelineEdit', function($injector) { scope.$evalAsync(kbnUrl.change(`/management/logstash/pipelines/${id}/edit`)); const userResource = logstashSecurity.isSecurityEnabled() - ? await shieldUser.getCurrent().$promise + ? await npSetup.plugins.security.authc.getCurrentUser() : null; render( diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 6ee8b5f8b2b10f..bc403b803b8df0 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -28,17 +28,10 @@ export const security = kibana => enabled: Joi.boolean().default(true), cookieName: HANDLED_IN_NEW_PLATFORM, encryptionKey: HANDLED_IN_NEW_PLATFORM, - session: Joi.object({ - idleTimeout: HANDLED_IN_NEW_PLATFORM, - lifespan: HANDLED_IN_NEW_PLATFORM, - }).default(), + session: HANDLED_IN_NEW_PLATFORM, secureCookies: HANDLED_IN_NEW_PLATFORM, loginAssistanceMessage: HANDLED_IN_NEW_PLATFORM, - authorization: Joi.object({ - legacyFallback: Joi.object({ - enabled: Joi.boolean().default(true), // deprecated - }).default(), - }).default(), + authorization: HANDLED_IN_NEW_PLATFORM, audit: Joi.object({ enabled: Joi.boolean().default(false), }).default(), @@ -46,13 +39,6 @@ export const security = kibana => }).default(); }, - deprecations: function({ rename, unused }) { - return [ - unused('authorization.legacyFallback.enabled'), - rename('sessionTimeout', 'session.idleTimeout'), - ]; - }, - uiExports: { chromeNavControls: [], managementSections: ['plugins/security/views/management'], diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts index ffa08ca44f3765..c5c6994bf4be36 100644 --- a/x-pack/legacy/plugins/security/public/lib/api.ts +++ b/x-pack/legacy/plugins/security/public/lib/api.ts @@ -5,16 +5,12 @@ */ import { kfetch } from 'ui/kfetch'; -import { AuthenticatedUser, Role, User, EditUser } from '../../common/model'; +import { Role, User, EditUser } from '../../common/model'; const usersUrl = '/internal/security/users'; const rolesUrl = '/api/security/role'; export class UserAPIClient { - public async getCurrentUser(): Promise { - return await kfetch({ pathname: `/internal/security/me` }); - } - public async getUsers(): Promise { return await kfetch({ pathname: usersUrl }); } diff --git a/x-pack/legacy/plugins/security/public/services/shield_user.js b/x-pack/legacy/plugins/security/public/services/shield_user.js deleted file mode 100644 index 14a79f267ca752..00000000000000 --- a/x-pack/legacy/plugins/security/public/services/shield_user.js +++ /dev/null @@ -1,33 +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 'angular-resource'; -import angular from 'angular'; -import { uiModules } from 'ui/modules'; - -const module = uiModules.get('security', ['ngResource']); -module.service('ShieldUser', ($resource, chrome) => { - const baseUrl = chrome.addBasePath('/internal/security/users/:username'); - const ShieldUser = $resource( - baseUrl, - { - username: '@username', - }, - { - changePassword: { - method: 'POST', - url: `${baseUrl}/password`, - transformRequest: ({ password, newPassword }) => angular.toJson({ password, newPassword }), - }, - getCurrent: { - method: 'GET', - url: chrome.addBasePath('/internal/security/me'), - }, - } - ); - - return ShieldUser; -}); diff --git a/x-pack/legacy/plugins/security/public/views/account/account.js b/x-pack/legacy/plugins/security/public/views/account/account.js index db971bd97eab73..70a7b8dce727ec 100644 --- a/x-pack/legacy/plugins/security/public/views/account/account.js +++ b/x-pack/legacy/plugins/security/public/views/account/account.js @@ -6,22 +6,13 @@ import routes from 'ui/routes'; import template from './account.html'; -import '../../services/shield_user'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; import { AccountManagementPage } from './components'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -const renderReact = (elem, user) => { - render( - - - , - elem - ); -}; - routes.when('/account', { template, k7Breadcrumbs: () => [ @@ -31,13 +22,8 @@ routes.when('/account', { }), }, ], - resolve: { - user(ShieldUser) { - return ShieldUser.getCurrent().$promise; - }, - }, controllerAs: 'accountController', - controller($scope, $route) { + controller($scope) { $scope.$on('$destroy', () => { const elem = document.getElementById('userProfileReactRoot'); if (elem) { @@ -45,8 +31,12 @@ routes.when('/account', { } }); $scope.$$postDigest(() => { - const elem = document.getElementById('userProfileReactRoot'); - renderReact(elem, $route.current.locals.user); + render( + + + , + document.getElementById('userProfileReactRoot') + ); }); }, }); diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx index 176b05f455439b..366842e58e9e4a 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from '@testing-library/react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { securityMock } from '../../../../../../../plugins/security/public/mocks'; import { AccountManagementPage } from './account_management_page'; +import { AuthenticatedUser } from '../../../../common/model'; jest.mock('ui/kfetch'); @@ -32,10 +35,24 @@ const createUser = ({ withFullName = true, withEmail = true, realm = 'native' }: }; }; +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`, () => { + it(`displays users full name, username, and email address`, async () => { const user = createUser(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( user.full_name ); @@ -43,28 +60,60 @@ describe('', () => { expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); }); - it(`displays username when full_name is not provided`, () => { + it(`displays username when full_name is not provided`, async () => { const user = createUser({ withFullName: false }); - const wrapper = mountWithIntl(); + 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`, () => { + it(`displays a placeholder when no email address is provided`, async () => { const user = createUser({ withEmail: false }); - const wrapper = mountWithIntl(); + 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`, () => { + it(`displays change password form for users in the native realm`, async () => { const user = createUser(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(1); expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(1); }); - it(`does not display change password form for users in the saml realm`, () => { + it(`does not display change password form for users in the saml realm`, async () => { const user = createUser({ realm: 'saml' }); - const wrapper = mountWithIntl(); + 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/legacy/plugins/security/public/views/account/components/account_management_page.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx index 2ed057ad73a123..6abee73e0b3535 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx @@ -4,29 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { SecurityPluginSetup } from '../../../../../../../plugins/security/public'; import { getUserDisplayName, AuthenticatedUser } from '../../../../common/model'; import { ChangePassword } from './change_password'; import { PersonalInfo } from './personal_info'; interface Props { - user: AuthenticatedUser; + securitySetup: SecurityPluginSetup; } -export const AccountManagementPage: React.FC = props => ( - - - - -

{getUserDisplayName(props.user)}

-
+export const AccountManagementPage = (props: Props) => { + const [currentUser, setCurrentUser] = useState(null); + useEffect(() => { + props.securitySetup.authc.getCurrentUser().then(setCurrentUser); + }, [props]); - + if (!currentUser) { + return null; + } - + return ( + + + + +

{getUserDisplayName(currentUser)}

+
- -
-
-
-); + + + + + +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js index 09c612526918fe..27c9beb4ba8284 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js @@ -11,10 +11,10 @@ import { kfetch } from 'ui/kfetch'; import { fatalError, toastNotifications } from 'ui/notify'; import { npStart } from 'ui/new_platform'; import template from 'plugins/security/views/management/edit_role/edit_role.html'; -import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import 'plugins/security/services/shield_indices'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +import { UserAPIClient } from '../../../lib/api'; import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs'; @@ -69,9 +69,8 @@ const routeDefinition = action => ({ return role.then(res => res.toJSON()); }, - users(ShieldUser) { - // $promise is used here because the result is an ngResource, not a promise itself - return ShieldUser.query().$promise.then(users => _.map(users, 'username')); + users() { + return new UserAPIClient().getUsers().then(users => _.map(users, 'username')); }, indexPatterns() { return npStart.plugins.data.indexPatterns.getTitles(); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx index 5c71d0da3954af..639646ce48e224 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx @@ -4,38 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from '@testing-library/react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { EditUserPage } from './edit_user_page'; import React from 'react'; +import { securityMock } from '../../../../../../../../plugins/security/public/mocks'; import { UserAPIClient } from '../../../../lib/api'; import { User, Role } from '../../../../../common/model'; import { ReactWrapper } from 'enzyme'; +import { mockAuthenticatedUser } from '../../../../../../../../plugins/security/common/model/authenticated_user.mock'; jest.mock('ui/kfetch'); -const buildClient = () => { - const apiClient = new UserAPIClient(); +const createUser = (username: string) => { + const user: User = { + username, + full_name: 'my full name', + email: 'foo@bar.com', + roles: ['idk', 'something'], + enabled: true, + }; - const createUser = (username: string) => { - const user: User = { - username, - full_name: 'my full name', - email: 'foo@bar.com', - roles: ['idk', 'something'], - enabled: true, + if (username === 'reserved_user') { + user.metadata = { + _reserved: true, }; + } - if (username === 'reserved_user') { - user.metadata = { - _reserved: true, - }; - } + return user; +}; - return Promise.resolve(user); - }; +const buildClient = () => { + const apiClient = new UserAPIClient(); - apiClient.getUser = jest.fn().mockImplementation(createUser); - apiClient.getCurrentUser = jest.fn().mockImplementation(() => createUser('current_user')); + apiClient.getUser = jest + .fn() + .mockImplementation(async (username: string) => createUser(username)); apiClient.getRoles = jest.fn().mockImplementation(() => { return Promise.resolve([ @@ -63,6 +67,14 @@ const buildClient = () => { return apiClient; }; +function buildSecuritySetup() { + const securitySetupMock = securityMock.createSetup(); + securitySetupMock.authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser(createUser('current_user')) + ); + return securitySetupMock; +} + function expectSaveButton(wrapper: ReactWrapper) { expect(wrapper.find('EuiButton[data-test-subj="userFormSaveButton"]')).toHaveLength(1); } @@ -74,10 +86,12 @@ function expectMissingSaveButton(wrapper: ReactWrapper) { describe('EditUserPage', () => { it('allows reserved users to be viewed', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -86,17 +100,19 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(1); - expect(apiClient.getCurrentUser).toBeCalledTimes(1); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1); expectMissingSaveButton(wrapper); }); it('allows new users to be created', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -105,17 +121,19 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(0); - expect(apiClient.getCurrentUser).toBeCalledTimes(0); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(0); expectSaveButton(wrapper); }); it('allows existing users to be edited', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -124,16 +142,15 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(1); - expect(apiClient.getCurrentUser).toBeCalledTimes(1); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1); expectSaveButton(wrapper); }); }); async function waitForRender(wrapper: ReactWrapper) { - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx index 91f5f048adc6d0..bbffe07485f8dc 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx @@ -28,6 +28,7 @@ import { } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react'; +import { SecurityPluginSetup } from '../../../../../../../../plugins/security/public'; import { UserValidator, UserValidationResult } from '../../../../lib/validate_user'; import { User, EditUser, Role } from '../../../../../common/model'; import { USERS_PATH } from '../../../../views/management/management_urls'; @@ -40,6 +41,7 @@ interface Props { intl: InjectedIntl; changeUrl: (path: string) => void; apiClient: UserAPIClient; + securitySetup: SecurityPluginSetup; } interface State { @@ -82,7 +84,7 @@ class EditUserPageUI extends Component { } public async componentDidMount() { - const { username, apiClient } = this.props; + const { username, apiClient, securitySetup } = this.props; let { user, currentUser } = this.state; if (username) { try { @@ -91,7 +93,7 @@ class EditUserPageUI extends Component { password: '', confirmPassword: '', }; - currentUser = await apiClient.getCurrentUser(); + currentUser = await securitySetup.authc.getCurrentUser(); } catch (err) { toastNotifications.addDanger({ title: this.props.intl.formatMessage({ diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js index bd9d6f2b1ca35c..ab218022c6ee64 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js @@ -7,7 +7,6 @@ import routes from 'ui/routes'; import template from 'plugins/security/views/management/edit_user/edit_user.html'; import 'angular-resource'; import 'ui/angular_ui_select'; -import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import { EDIT_USERS_PATH } from '../management_urls'; import { EditUserPage } from './components'; @@ -15,12 +14,18 @@ import { UserAPIClient } from '../../../lib/api'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; import { getEditUserBreadcrumbs, getCreateUserBreadcrumbs } from '../breadcrumbs'; const renderReact = (elem, changeUrl, username) => { render( - + , elem ); diff --git a/x-pack/legacy/plugins/security/public/views/management/management.js b/x-pack/legacy/plugins/security/public/views/management/management.js index db2175e91c5de9..59da63abbb83ff 100644 --- a/x-pack/legacy/plugins/security/public/views/management/management.js +++ b/x-pack/legacy/plugins/security/public/views/management/management.js @@ -13,10 +13,10 @@ import 'plugins/security/views/management/edit_user/edit_user'; import 'plugins/security/views/management/edit_role/index'; import routes from 'ui/routes'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import '../../services/shield_user'; import { ROLES_PATH, USERS_PATH, API_KEYS_PATH } from './management_urls'; import { management } from 'ui/management'; +import { npSetup } from 'ui/new_platform'; import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; @@ -36,7 +36,7 @@ routes }) .defaults(/\/management/, { resolve: { - securityManagementSection: function(ShieldUser) { + securityManagementSection: function() { const showSecurityLinks = xpackInfo.get('features.security.showLinks'); function deregisterSecurity() { @@ -93,12 +93,11 @@ routes if (!showSecurityLinks) { deregisterSecurity(); } else { - // getCurrent will reject if there is no authenticated user, so we prevent them from seeing the security - // management screens - // - // $promise is used here because the result is an ngResource, not a promise itself - return ShieldUser.getCurrent() - .$promise.then(ensureSecurityRegistered) + // getCurrentUser will reject if there is no authenticated user, so we prevent them from + // seeing the security management screens. + return npSetup.plugins.security.authc + .getCurrentUser() + .then(ensureSecurityRegistered) .catch(deregisterSecurity); } }, diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js index a7115f449ebfd5..8d4e0526251d76 100644 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js +++ b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js @@ -8,7 +8,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import routes from 'ui/routes'; import template from 'plugins/security/views/management/users_grid/users.html'; -import 'plugins/security/services/shield_user'; import { SECURITY_PATH, USERS_PATH } from '../management_urls'; import { UsersListPage } from './components'; import { UserAPIClient } from '../../../lib/api'; diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx index 76088443212b24..fb39c517e1c2ca 100644 --- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx +++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx @@ -10,36 +10,40 @@ import React from 'react'; import { render } from 'react-dom'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; +import { SecurityPluginSetup } from '../../../../../../plugins/security/public'; import { AuthenticatedUser } from '../../../common/model'; import { AuthenticationStatePage } from '../../components/authentication_state_page'; chrome .setVisible(false) .setRootTemplate('
') - .setRootController('overwritten_session', ($scope: any, ShieldUser: any) => { + .setRootController('overwritten_session', ($scope: any) => { $scope.$$postDigest(() => { - ShieldUser.getCurrent().$promise.then((user: AuthenticatedUser) => { - const overwrittenSessionPage = ( - - - } - > - - - - - - ); - render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot')); - }); + ((npSetup.plugins as unknown) as { security: SecurityPluginSetup }).security.authc + .getCurrentUser() + .then((user: AuthenticatedUser) => { + const overwrittenSessionPage = ( + + + } + > + + + + + + ); + render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot')); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index 2e16f209acfb17..edf196b96f5d02 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -283,9 +283,7 @@ export const getResult = (): RuleAlertType => ({ ], riskScore: 50, maxSignals: 100, - size: 1, severity: 'high', - tags: [], to: 'now', type: 'query', threats: [ diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts index c1058bd353e8ce..5f69082e3fc719 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts @@ -5,7 +5,7 @@ */ import { SIGNALS_ID } from '../../../../common/constants'; -import { FindRuleParams } from './types'; +import { FindRuleParams, RuleAlertType } from './types'; export const getFilter = (filter: string | null | undefined) => { if (filter == null) { @@ -33,5 +33,10 @@ export const findRules = async ({ sortOrder, sortField, }, - }); + }) as Promise<{ + page: number; + perPage: number; + total: number; + data: RuleAlertType[]; + }>; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index b0578174e1f658..4f4c0da7127cd7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -35,10 +35,9 @@ export interface BulkUpdateRulesRequest extends RequestFacade { payload: UpdateRuleAlertParamsRest[]; } -export type RuleAlertType = Alert & { - id: string; +export interface RuleAlertType extends Alert { params: RuleTypeParams; -}; +} export interface RulesRequest extends RequestFacade { payload: RuleAlertParamsRest; diff --git a/x-pack/plugins/security/public/authentication/authentication_service.ts b/x-pack/plugins/security/public/authentication/authentication_service.ts new file mode 100644 index 00000000000000..23c45c88e563a1 --- /dev/null +++ b/x-pack/plugins/security/public/authentication/authentication_service.ts @@ -0,0 +1,31 @@ +/* + * 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 { HttpSetup } from 'src/core/public'; +import { AuthenticatedUser } from '../../common/model'; + +interface SetupParams { + http: HttpSetup; +} + +export interface AuthenticationServiceSetup { + /** + * Returns currently authenticated user and throws if current user isn't authenticated. + */ + getCurrentUser: () => Promise; +} + +export class AuthenticationService { + public setup({ http }: SetupParams): AuthenticationServiceSetup { + return { + async getCurrentUser() { + return (await http.get('/internal/security/me', { + headers: { 'kbn-system-api': true }, + })) as AuthenticatedUser; + }, + }; + } +} diff --git a/x-pack/plugins/security/public/authentication/index.mock.ts b/x-pack/plugins/security/public/authentication/index.mock.ts new file mode 100644 index 00000000000000..c8d77a5b62c6f2 --- /dev/null +++ b/x-pack/plugins/security/public/authentication/index.mock.ts @@ -0,0 +1,13 @@ +/* + * 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 { AuthenticationServiceSetup } from './authentication_service'; + +export const authenticationMock = { + createSetup: (): jest.Mocked => ({ + getCurrentUser: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/authentication/index.ts b/x-pack/plugins/security/public/authentication/index.ts new file mode 100644 index 00000000000000..a55f4d7bb95b38 --- /dev/null +++ b/x-pack/plugins/security/public/authentication/index.ts @@ -0,0 +1,7 @@ +/* + * 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 { AuthenticationService, AuthenticationServiceSetup } from './authentication_service'; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index dc34fcbbe7d1e6..336ec37d76a1b9 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -6,7 +6,10 @@ import { PluginInitializer } from 'src/core/public'; import { SecurityPlugin, SecurityPluginSetup, SecurityPluginStart } from './plugin'; + +export { SecurityPluginSetup, SecurityPluginStart }; export { SessionInfo } from './types'; +export { AuthenticatedUser } from '../common/model'; export const plugin: PluginInitializer = () => new SecurityPlugin(); diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts new file mode 100644 index 00000000000000..3c0c59d10abd1a --- /dev/null +++ b/x-pack/plugins/security/public/mocks.ts @@ -0,0 +1,19 @@ +/* + * 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 { authenticationMock } from './authentication/index.mock'; +import { createSessionTimeoutMock } from './session/session_timeout.mock'; + +function createSetupMock() { + return { + authc: authenticationMock.createSetup(), + sessionTimeout: createSessionTimeoutMock(), + }; +} + +export const securityMock = { + createSetup: createSetupMock, +}; 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 3879d611d46ebf..a9a89ee05f561e 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 @@ -10,6 +10,8 @@ import { ILicense } from '../../../licensing/public'; import { SecurityNavControlService } from '.'; import { SecurityLicenseService } from '../../common/licensing'; import { nextTick } from 'test_utils/enzyme_helpers'; +import { securityMock } from '../mocks'; +import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; const validLicense = { isAvailable: true, @@ -29,13 +31,17 @@ describe('SecurityNavControlService', () => { const license$ = new BehaviorSubject(validLicense); const navControlService = new SecurityNavControlService(); + const mockSecuritySetup = securityMock.createSetup(); + mockSecuritySetup.authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser({ username: 'some-user', full_name: undefined }) + ); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: mockSecuritySetup.authc, }); const coreStart = coreMock.createStart(); coreStart.chrome.navControls.registerRight = jest.fn(); - coreStart.http.get.mockResolvedValue({ username: 'some-user' }); navControlService.start({ core: coreStart }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(1); @@ -93,6 +99,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -111,6 +118,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -126,6 +134,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -146,6 +155,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index aeeb84219c937d..153e7112dc95b3 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -9,11 +9,12 @@ import { CoreStart } from 'src/core/public'; import ReactDOM from 'react-dom'; import React from 'react'; import { SecurityLicense } from '../../common/licensing'; -import { AuthenticatedUser } from '../../common/model'; import { SecurityNavControl } from './nav_control_component'; +import { AuthenticationServiceSetup } from '../authentication'; interface SetupDeps { securityLicense: SecurityLicense; + authc: AuthenticationServiceSetup; } interface StartDeps { @@ -22,13 +23,15 @@ interface StartDeps { export class SecurityNavControlService { private securityLicense!: SecurityLicense; + private authc!: AuthenticationServiceSetup; private navControlRegistered!: boolean; private securityFeaturesSubscription?: Subscription; - public setup({ securityLicense }: SetupDeps) { + public setup({ securityLicense, authc }: SetupDeps) { this.securityLicense = securityLicense; + this.authc = authc; } public start({ core }: StartDeps) { @@ -38,14 +41,8 @@ export class SecurityNavControlService { const shouldRegisterNavControl = !isAnonymousPath && showLinks && !this.navControlRegistered; - if (shouldRegisterNavControl) { - const user = core.http.get('/internal/security/me', { - headers: { - 'kbn-system-api': true, - }, - }) as Promise; - this.registerSecurityNavControl(core, user); + this.registerSecurityNavControl(core); } } ); @@ -60,16 +57,16 @@ export class SecurityNavControlService { } private registerSecurityNavControl( - core: Pick, - user: Promise + core: Pick ) { + const currentUserPromise = this.authc.getCurrentUser(); core.chrome.navControls.registerRight({ order: 2000, mount: (el: HTMLElement) => { const I18nContext = core.i18n.Context; const props = { - user, + user: currentUserPromise, editProfileUrl: core.http.basePath.prepend('/app/kibana#/account'), logoutUrl: core.http.basePath.prepend(`/logout`), }; diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts index 0f10f9d89f25a2..50e0b838c750fc 100644 --- a/x-pack/plugins/security/public/plugin.ts +++ b/x-pack/plugins/security/public/plugin.ts @@ -9,18 +9,20 @@ import { LicensingPluginSetup } from '../../licensing/public'; import { SessionExpired, SessionTimeout, + ISessionTimeout, SessionTimeoutHttpInterceptor, UnauthorizedResponseHttpInterceptor, } from './session'; import { SecurityLicenseService } from '../common/licensing'; import { SecurityNavControlService } from './nav_control'; +import { AuthenticationService } from './authentication'; export interface PluginSetupDependencies { licensing: LicensingPluginSetup; } export class SecurityPlugin implements Plugin { - private sessionTimeout!: SessionTimeout; + private sessionTimeout!: ISessionTimeout; private navControlService!: SecurityNavControlService; @@ -43,12 +45,15 @@ export class SecurityPlugin implements Plugin; private sessionInfo?: SessionInfo; private fetchTimer?: number; diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index b3f96497b0538b..4f1c25702ae974 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -8,7 +8,6 @@ import crypto from 'crypto'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { schema, Type, TypeOf } from '@kbn/config-schema'; -import { duration } from 'moment'; import { PluginInitializerContext } from '../../../../src/core/server'; export type ConfigType = ReturnType extends Observable @@ -35,7 +34,6 @@ export const ConfigSchema = schema.object( schema.maybe(schema.string({ minLength: 32 })), schema.string({ minLength: 32, defaultValue: 'a'.repeat(32) }) ), - sessionTimeout: schema.maybe(schema.nullable(schema.number())), // DEPRECATED session: schema.object({ idleTimeout: schema.nullable(schema.duration()), lifespan: schema.nullable(schema.duration()), @@ -88,22 +86,11 @@ export function createConfig$(context: PluginInitializerContext, isTLSEnabled: b secureCookies = true; } - // "sessionTimeout" is deprecated and replaced with "session.idleTimeout" - // however, NP does not yet have a mechanism to automatically rename deprecated keys - // for the time being, we'll do it manually: - const deprecatedSessionTimeout = - typeof config.sessionTimeout === 'number' ? duration(config.sessionTimeout) : null; - const val = { + return { ...config, encryptionKey, secureCookies, - session: { - ...config.session, - idleTimeout: config.session.idleTimeout || deprecatedSessionTimeout, - }, }; - delete val.sessionTimeout; // DEPRECATED - return val; }) ); } diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index e189b71345ffc9..33f554be5caa3b 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -4,9 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { + PluginConfigDescriptor, + PluginInitializer, + PluginInitializerContext, + RecursiveReadonly, +} from '../../../../src/core/server'; import { ConfigSchema } from './config'; -import { Plugin } from './plugin'; +import { Plugin, PluginSetupContract, PluginSetupDependencies } from './plugin'; // These exports are part of public Security plugin contract, any change in signature of exported // functions or removal of exports should be considered as a breaking change. @@ -17,8 +23,17 @@ export { InvalidateAPIKeyParams, InvalidateAPIKeyResult, } from './authentication'; -export { PluginSetupContract } from './plugin'; +export { PluginSetupContract }; -export const config = { schema: ConfigSchema }; -export const plugin = (initializerContext: PluginInitializerContext) => - new Plugin(initializerContext); +export const config: PluginConfigDescriptor> = { + schema: ConfigSchema, + deprecations: ({ rename, unused }) => [ + rename('sessionTimeout', 'session.idleTimeout'), + unused('authorization.legacyFallback.enabled'), + ], +}; +export const plugin: PluginInitializer< + RecursiveReadonly, + void, + PluginSetupDependencies +> = (initializerContext: PluginInitializerContext) => new Plugin(initializerContext); diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index cdd2a024310bbc..9c4b01f94ef4df 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -110,10 +110,7 @@ export class Plugin { this.logger = this.initializerContext.logger.get(); } - public async setup( - core: CoreSetup, - { features, licensing }: PluginSetupDependencies - ): Promise> { + public async setup(core: CoreSetup, { features, licensing }: PluginSetupDependencies) { const [config, legacyConfig] = await combineLatest([ createConfig$(this.initializerContext, core.http.isTlsEnabled), this.initializerContext.config.legacy.globalConfig$, @@ -169,7 +166,7 @@ export class Plugin { csp: core.http.csp, }); - return deepFreeze({ + return deepFreeze({ authc, authz: { diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d957e451fdb748..83ef497e50649a 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -1773,10 +1773,7 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "にアクセスして有効な別のドキュメントを選択してください。", "kbn.context.unableToLoadAnchorDocumentDescription": "別のドキュメントが読み込めません", "kbn.context.unableToLoadDocumentDescription": "ドキュメントが読み込めません", - "kbn.dashboard.addVisualizationDescription1": "上のメニューバーの ", - "kbn.dashboard.addVisualizationDescription2": " ボタンをクリックして、ダッシュボードにビジュアライゼーションを追加します。", "kbn.dashboard.addVisualizationLinkAriaLabel": "ビジュアライゼーションを追加", - "kbn.dashboard.addVisualizationLinkText": "追加", "kbn.dashboard.badge.readOnly.text": "読み込み専用", "kbn.dashboard.badge.readOnly.tooltip": "ダッシュボードを保存できません", "kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel": "編集を続行", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2e47c7a615e364..87c11adcb5e777 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -1774,10 +1774,7 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "以选择有效地定位点文档。", "kbn.context.unableToLoadAnchorDocumentDescription": "无法加载该定位点文档", "kbn.context.unableToLoadDocumentDescription": "无法加载文档", - "kbn.dashboard.addVisualizationDescription1": "单击上述菜单栏中的 ", - "kbn.dashboard.addVisualizationDescription2": " 按钮,以将可视化添加到仪表板。", "kbn.dashboard.addVisualizationLinkAriaLabel": "添加可视化", - "kbn.dashboard.addVisualizationLinkText": "添加", "kbn.dashboard.badge.readOnly.text": "只读", "kbn.dashboard.badge.readOnly.tooltip": "无法保存仪表板", "kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel": "继续编辑", diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index c7a9764c6fb58e..aa6860b35763f8 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -107,7 +107,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { shouldLoginIfPrompted: false, } ); - await testSubjects.existOrFail('emptyDashboardAddPanelButton', { timeout: 10000 }); + await testSubjects.existOrFail('emptyDashboardWidget', { timeout: 10000 }); }); it(`can view existing Dashboard`, async () => { diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts index 127141b156cd80..c1197fa7023c51 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts @@ -73,7 +73,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { shouldLoginIfPrompted: false, } ); - await testSubjects.existOrFail('emptyDashboardAddPanelButton', { timeout: 10000 }); + await testSubjects.existOrFail('emptyDashboardWidget', { timeout: 10000 }); }); it(`can view existing Dashboard`, async () => {