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 () => {