;
+ beforeEach(() => {
+ const [acsRouteConfig, acsRouteHandler] = router.post.mock.calls.find(
+ ([{ path }]) => path === '/internal/security/access_agreement/acknowledge'
+ )!;
+
+ license.getFeatures.mockReturnValue({
+ allowAccessAgreement: true,
+ } as SecurityLicenseFeatures);
+
+ routeConfig = acsRouteConfig;
+ routeHandler = acsRouteHandler;
+ });
+
+ it('correctly defines route.', () => {
+ expect(routeConfig.options).toBeUndefined();
+ expect(routeConfig.validate).toBe(false);
+ });
+
+ it(`returns 403 if current license doesn't allow access agreement acknowledgement.`, async () => {
+ license.getFeatures.mockReturnValue({
+ allowAccessAgreement: false,
+ } as SecurityLicenseFeatures);
+
+ const request = httpServerMock.createKibanaRequest();
+ await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({
+ status: 403,
+ payload: { message: `Current license doesn't support access agreement.` },
+ options: { body: { message: `Current license doesn't support access agreement.` } },
+ });
+ });
+
+ it('returns 500 if acknowledge throws unhandled exception.', async () => {
+ const unhandledException = new Error('Something went wrong.');
+ authc.acknowledgeAccessAgreement.mockRejectedValue(unhandledException);
+
+ const request = httpServerMock.createKibanaRequest();
+ await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({
+ status: 500,
+ payload: 'Internal Error',
+ options: {},
+ });
+ });
+
+ it('returns 204 if successfully acknowledged.', async () => {
+ authc.acknowledgeAccessAgreement.mockResolvedValue(undefined);
+
+ const request = httpServerMock.createKibanaRequest();
+ await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({
+ status: 204,
+ options: {},
+ });
+ });
+ });
});
diff --git a/x-pack/plugins/security/server/routes/authentication/common.ts b/x-pack/plugins/security/server/routes/authentication/common.ts
index abab67c9cd1d28..91783140539a5b 100644
--- a/x-pack/plugins/security/server/routes/authentication/common.ts
+++ b/x-pack/plugins/security/server/routes/authentication/common.ts
@@ -18,7 +18,13 @@ import { RouteDefinitionParams } from '..';
/**
* Defines routes that are common to various authentication mechanisms.
*/
-export function defineCommonRoutes({ router, authc, basePath, logger }: RouteDefinitionParams) {
+export function defineCommonRoutes({
+ router,
+ authc,
+ basePath,
+ license,
+ logger,
+}: RouteDefinitionParams) {
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
for (const path of ['/api/security/logout', '/api/security/v1/logout']) {
router.get(
@@ -135,4 +141,26 @@ export function defineCommonRoutes({ router, authc, basePath, logger }: RouteDef
}
})
);
+
+ router.post(
+ { path: '/internal/security/access_agreement/acknowledge', validate: false },
+ createLicensedRouteHandler(async (context, request, response) => {
+ // If license doesn't allow access agreement we shouldn't handle request.
+ if (!license.getFeatures().allowAccessAgreement) {
+ logger.warn(`Attempted to acknowledge access agreement when license doesn't allow it.`);
+ return response.forbidden({
+ body: { message: `Current license doesn't support access agreement.` },
+ });
+ }
+
+ try {
+ await authc.acknowledgeAccessAgreement(request);
+ } catch (err) {
+ logger.error(err);
+ return response.internalError();
+ }
+
+ return response.noContent();
+ })
+ );
}
diff --git a/x-pack/plugins/security/server/routes/licensed_route_handler.ts b/x-pack/plugins/security/server/routes/licensed_route_handler.ts
index b113b2ca59e3ef..d8c212aa2d2174 100644
--- a/x-pack/plugins/security/server/routes/licensed_route_handler.ts
+++ b/x-pack/plugins/security/server/routes/licensed_route_handler.ts
@@ -4,10 +4,22 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { RequestHandler } from 'kibana/server';
+import { KibanaResponseFactory, RequestHandler, RouteMethod } from 'kibana/server';
-export const createLicensedRouteHandler = (handler: RequestHandler
) => {
- const licensedRouteHandler: RequestHandler
= (context, request, responseToolkit) => {
+export const createLicensedRouteHandler = <
+ P,
+ Q,
+ B,
+ M extends RouteMethod,
+ R extends KibanaResponseFactory
+>(
+ handler: RequestHandler
+) => {
+ const licensedRouteHandler: RequestHandler
= (
+ context,
+ request,
+ responseToolkit
+ ) => {
const { license } = context.licensing;
const licenseCheck = license.check('security', 'basic');
if (licenseCheck.state === 'unavailable' || licenseCheck.state === 'invalid') {
diff --git a/x-pack/plugins/security/server/routes/users/change_password.test.ts b/x-pack/plugins/security/server/routes/users/change_password.test.ts
index fd05821f9d5206..c163ff4e256cd2 100644
--- a/x-pack/plugins/security/server/routes/users/change_password.test.ts
+++ b/x-pack/plugins/security/server/routes/users/change_password.test.ts
@@ -53,7 +53,7 @@ describe('Change password', () => {
now: Date.now(),
idleTimeoutExpiration: null,
lifespanExpiration: null,
- provider: 'basic',
+ provider: { type: 'basic', name: 'basic' },
});
mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient();
diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.test.ts b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts
new file mode 100644
index 00000000000000..3d616575b84131
--- /dev/null
+++ b/x-pack/plugins/security/server/routes/views/access_agreement.test.ts
@@ -0,0 +1,177 @@
+/*
+ * 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 {
+ RequestHandler,
+ RouteConfig,
+ kibanaResponseFactory,
+ IRouter,
+ HttpResources,
+ HttpResourcesRequestHandler,
+ RequestHandlerContext,
+} from '../../../../../../src/core/server';
+import { SecurityLicense, SecurityLicenseFeatures } from '../../../common/licensing';
+import { AuthenticationProvider } from '../../../common/types';
+import { ConfigType } from '../../config';
+import { defineAccessAgreementRoutes } from './access_agreement';
+
+import { httpResourcesMock, httpServerMock } from '../../../../../../src/core/server/mocks';
+import { routeDefinitionParamsMock } from '../index.mock';
+import { Authentication } from '../../authentication';
+
+describe('Access agreement view routes', () => {
+ let httpResources: jest.Mocked;
+ let router: jest.Mocked;
+ let config: ConfigType;
+ let authc: jest.Mocked;
+ let license: jest.Mocked;
+ let mockContext: RequestHandlerContext;
+ beforeEach(() => {
+ const routeParamsMock = routeDefinitionParamsMock.create();
+ router = routeParamsMock.router;
+ httpResources = routeParamsMock.httpResources;
+ authc = routeParamsMock.authc;
+ config = routeParamsMock.config;
+ license = routeParamsMock.license;
+
+ license.getFeatures.mockReturnValue({
+ allowAccessAgreement: true,
+ } as SecurityLicenseFeatures);
+
+ mockContext = ({
+ licensing: {
+ license: { check: jest.fn().mockReturnValue({ check: 'valid' }) },
+ },
+ } as unknown) as RequestHandlerContext;
+
+ defineAccessAgreementRoutes(routeParamsMock);
+ });
+
+ describe('View route', () => {
+ let routeHandler: HttpResourcesRequestHandler;
+ let routeConfig: RouteConfig;
+ beforeEach(() => {
+ const [viewRouteConfig, viewRouteHandler] = httpResources.register.mock.calls.find(
+ ([{ path }]) => path === '/security/access_agreement'
+ )!;
+
+ routeConfig = viewRouteConfig;
+ routeHandler = viewRouteHandler;
+ });
+
+ it('correctly defines route.', () => {
+ expect(routeConfig.options).toBeUndefined();
+ expect(routeConfig.validate).toBe(false);
+ });
+
+ it('does not render view if current license does not allow access agreement.', async () => {
+ const request = httpServerMock.createKibanaRequest();
+ const responseFactory = httpResourcesMock.createResponseFactory();
+
+ license.getFeatures.mockReturnValue({
+ allowAccessAgreement: false,
+ } as SecurityLicenseFeatures);
+
+ await routeHandler(mockContext, request, responseFactory);
+
+ expect(responseFactory.renderCoreApp).not.toHaveBeenCalledWith();
+ expect(responseFactory.forbidden).toHaveBeenCalledTimes(1);
+ });
+
+ it('renders view.', async () => {
+ const request = httpServerMock.createKibanaRequest();
+ const responseFactory = httpResourcesMock.createResponseFactory();
+
+ await routeHandler(mockContext, request, responseFactory);
+
+ expect(responseFactory.renderCoreApp).toHaveBeenCalledWith();
+ });
+ });
+
+ describe('Access agreement state route', () => {
+ let routeHandler: RequestHandler;
+ let routeConfig: RouteConfig;
+ beforeEach(() => {
+ const [loginStateRouteConfig, loginStateRouteHandler] = router.get.mock.calls.find(
+ ([{ path }]) => path === '/internal/security/access_agreement/state'
+ )!;
+
+ routeConfig = loginStateRouteConfig;
+ routeHandler = loginStateRouteHandler;
+ });
+
+ it('correctly defines route.', () => {
+ expect(routeConfig.options).toBeUndefined();
+ expect(routeConfig.validate).toBe(false);
+ });
+
+ it('returns `403` if current license does not allow access agreement.', async () => {
+ const request = httpServerMock.createKibanaRequest();
+
+ license.getFeatures.mockReturnValue({
+ allowAccessAgreement: false,
+ } as SecurityLicenseFeatures);
+
+ await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({
+ status: 403,
+ payload: { message: `Current license doesn't support access agreement.` },
+ options: { body: { message: `Current license doesn't support access agreement.` } },
+ });
+ });
+
+ it('returns empty `accessAgreement` if session info is not available.', async () => {
+ const request = httpServerMock.createKibanaRequest();
+
+ authc.getSessionInfo.mockResolvedValue(null);
+
+ await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({
+ options: { body: { accessAgreement: '' } },
+ payload: { accessAgreement: '' },
+ status: 200,
+ });
+ });
+
+ it('returns non-empty `accessAgreement` only if it is configured.', async () => {
+ const request = httpServerMock.createKibanaRequest();
+
+ config.authc = routeDefinitionParamsMock.create({
+ authc: {
+ providers: {
+ basic: { basic1: { order: 0 } },
+ saml: {
+ saml1: {
+ order: 1,
+ realm: 'realm1',
+ accessAgreement: { message: 'Some access agreement' },
+ },
+ },
+ },
+ },
+ }).config.authc;
+
+ const cases: Array<[AuthenticationProvider, string]> = [
+ [{ type: 'basic', name: 'basic1' }, ''],
+ [{ type: 'saml', name: 'saml1' }, 'Some access agreement'],
+ [{ type: 'unknown-type', name: 'unknown-name' }, ''],
+ ];
+
+ for (const [sessionProvider, expectedAccessAgreement] of cases) {
+ authc.getSessionInfo.mockResolvedValue({
+ now: Date.now(),
+ idleTimeoutExpiration: null,
+ lifespanExpiration: null,
+ provider: sessionProvider,
+ });
+
+ await expect(routeHandler(mockContext, request, kibanaResponseFactory)).resolves.toEqual({
+ options: { body: { accessAgreement: expectedAccessAgreement } },
+ payload: { accessAgreement: expectedAccessAgreement },
+ status: 200,
+ });
+ }
+ });
+ });
+});
diff --git a/x-pack/plugins/security/server/routes/views/access_agreement.ts b/x-pack/plugins/security/server/routes/views/access_agreement.ts
new file mode 100644
index 00000000000000..49e1ff42a28a2a
--- /dev/null
+++ b/x-pack/plugins/security/server/routes/views/access_agreement.ts
@@ -0,0 +1,64 @@
+/*
+ * 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 { ConfigType } from '../../config';
+import { createLicensedRouteHandler } from '../licensed_route_handler';
+import { RouteDefinitionParams } from '..';
+
+/**
+ * Defines routes required for the Access Agreement view.
+ */
+export function defineAccessAgreementRoutes({
+ authc,
+ httpResources,
+ license,
+ config,
+ router,
+ logger,
+}: RouteDefinitionParams) {
+ // If license doesn't allow access agreement we shouldn't handle request.
+ const canHandleRequest = () => license.getFeatures().allowAccessAgreement;
+
+ httpResources.register(
+ { path: '/security/access_agreement', validate: false },
+ createLicensedRouteHandler(async (context, request, response) =>
+ canHandleRequest()
+ ? response.renderCoreApp()
+ : response.forbidden({
+ body: { message: `Current license doesn't support access agreement.` },
+ })
+ )
+ );
+
+ router.get(
+ { path: '/internal/security/access_agreement/state', validate: false },
+ createLicensedRouteHandler(async (context, request, response) => {
+ if (!canHandleRequest()) {
+ return response.forbidden({
+ body: { message: `Current license doesn't support access agreement.` },
+ });
+ }
+
+ // It's not guaranteed that we'll have session for the authenticated user (e.g. when user is
+ // authenticated with the help of HTTP authentication), that means we should safely check if
+ // we have it and can get a corresponding configuration.
+ try {
+ const session = await authc.getSessionInfo(request);
+ const accessAgreement =
+ (session &&
+ config.authc.providers[
+ session.provider.type as keyof ConfigType['authc']['providers']
+ ]?.[session.provider.name]?.accessAgreement?.message) ||
+ '';
+
+ return response.ok({ body: { accessAgreement } });
+ } catch (err) {
+ logger.error(err);
+ return response.internalError();
+ }
+ })
+ );
+}
diff --git a/x-pack/plugins/security/server/routes/views/index.test.ts b/x-pack/plugins/security/server/routes/views/index.test.ts
index a8e7e905b119af..7cddef9bf2b982 100644
--- a/x-pack/plugins/security/server/routes/views/index.test.ts
+++ b/x-pack/plugins/security/server/routes/views/index.test.ts
@@ -20,15 +20,18 @@ describe('View routes', () => {
expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path))
.toMatchInlineSnapshot(`
Array [
+ "/security/access_agreement",
"/security/account",
"/security/logged_out",
"/logout",
"/security/overwritten_session",
]
`);
- expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(
- `Array []`
- );
+ expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
+ Array [
+ "/internal/security/access_agreement/state",
+ ]
+ `);
});
it('registers Login routes if `basic` provider is enabled', () => {
@@ -43,6 +46,7 @@ describe('View routes', () => {
.toMatchInlineSnapshot(`
Array [
"/login",
+ "/security/access_agreement",
"/security/account",
"/security/logged_out",
"/logout",
@@ -52,6 +56,7 @@ describe('View routes', () => {
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
Array [
"/internal/security/login_state",
+ "/internal/security/access_agreement/state",
]
`);
});
@@ -68,6 +73,7 @@ describe('View routes', () => {
.toMatchInlineSnapshot(`
Array [
"/login",
+ "/security/access_agreement",
"/security/account",
"/security/logged_out",
"/logout",
@@ -77,6 +83,7 @@ describe('View routes', () => {
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
Array [
"/internal/security/login_state",
+ "/internal/security/access_agreement/state",
]
`);
});
@@ -93,6 +100,7 @@ describe('View routes', () => {
.toMatchInlineSnapshot(`
Array [
"/login",
+ "/security/access_agreement",
"/security/account",
"/security/logged_out",
"/logout",
@@ -102,6 +110,7 @@ describe('View routes', () => {
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
Array [
"/internal/security/login_state",
+ "/internal/security/access_agreement/state",
]
`);
});
diff --git a/x-pack/plugins/security/server/routes/views/index.ts b/x-pack/plugins/security/server/routes/views/index.ts
index 255989dfeb90cd..b9de58d47fe407 100644
--- a/x-pack/plugins/security/server/routes/views/index.ts
+++ b/x-pack/plugins/security/server/routes/views/index.ts
@@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
+import { defineAccessAgreementRoutes } from './access_agreement';
import { defineAccountManagementRoutes } from './account_management';
import { defineLoggedOutRoutes } from './logged_out';
import { defineLoginRoutes } from './login';
@@ -20,6 +21,7 @@ export function defineViewRoutes(params: RouteDefinitionParams) {
defineLoginRoutes(params);
}
+ defineAccessAgreementRoutes(params);
defineAccountManagementRoutes(params);
defineLoggedOutRoutes(params);
defineLogoutRoutes(params);
diff --git a/x-pack/plugins/security/server/routes/views/logged_out.test.ts b/x-pack/plugins/security/server/routes/views/logged_out.test.ts
index 3ff05d242d9dde..7cb73c49f9cbc8 100644
--- a/x-pack/plugins/security/server/routes/views/logged_out.test.ts
+++ b/x-pack/plugins/security/server/routes/views/logged_out.test.ts
@@ -39,7 +39,7 @@ describe('LoggedOut view routes', () => {
it('redirects user to the root page if they have a session already.', async () => {
authc.getSessionInfo.mockResolvedValue({
- provider: 'basic',
+ provider: { type: 'basic', name: 'basic' },
now: 0,
idleTimeoutExpiration: null,
lifespanExpiration: null,
diff --git a/x-pack/plugins/security/server/routes/views/login.test.ts b/x-pack/plugins/security/server/routes/views/login.test.ts
index 8bc2bb32325fc0..014ad390a3d53b 100644
--- a/x-pack/plugins/security/server/routes/views/login.test.ts
+++ b/x-pack/plugins/security/server/routes/views/login.test.ts
@@ -163,6 +163,7 @@ describe('Login view routes', () => {
it('returns only required license features.', async () => {
license.getFeatures.mockReturnValue({
+ allowAccessAgreement: true,
allowLogin: true,
allowRbac: false,
allowRoleDocumentLevelSecurity: true,
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index ac074d99e9ff50..cdff34ec3a6039 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -2579,7 +2579,6 @@
"telemetry.welcomeBanner.enableButtonLabel": "有効にする",
"telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遠隔測定に関するプライバシーステートメント",
"telemetry.welcomeBanner.title": "Elastic Stack の改善にご協力ください",
- "tileMap.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子は data-update に対応できるようこのメソドを導入する必要があります",
"tileMap.function.help": "タイルマップのビジュアライゼーションです",
"tileMap.geohashLayer.mapTitle": "{mapType} マップタイプが認識されません",
"tileMap.tooltipFormatter.latitudeLabel": "緯度",
@@ -2601,25 +2600,6 @@
"tileMap.visParams.desaturateTilesLabel": "タイルを不飽和化",
"tileMap.visParams.mapTypeLabel": "マップタイプ",
"tileMap.visParams.reduceVibrancyOfTileColorsTip": "色の鮮明度を下げます。この機能は Internet Explorer ではバージョンにかかわらず利用できません。",
- "tileMap.wmsOptions.attributionStringTip": "右下角の属性文字列",
- "tileMap.wmsOptions.baseLayerSettingsTitle": "ベースレイヤー設定",
- "tileMap.wmsOptions.imageFormatToUseTip": "通常画像/png または画像/jpeg です。サーバーが透明レイヤーを返す場合は png を使用します。",
- "tileMap.wmsOptions.layersLabel": "レイヤー",
- "tileMap.wmsOptions.listOfLayersToUseTip": "使用するレイヤーのコンマ区切りのリストです。",
- "tileMap.wmsOptions.mapLoadFailDescription": "このパラメーターが正しくないと、マップが正常に読み込まれません。",
- "tileMap.wmsOptions.urlOfWMSWebServiceTip": "WMS web サービスの URL です。",
- "tileMap.wmsOptions.useWMSCompliantMapTileServerTip": "WMS 対応のマップタイルサーバーを使用します。上級者向けです。",
- "tileMap.wmsOptions.versionOfWMSserverSupportsTip": "サーバーがサポートしている WMS のバージョンです。",
- "tileMap.wmsOptions.wmsAttributionLabel": "WMS 属性",
- "tileMap.wmsOptions.wmsDescription": "WMS は、マップイメージサービスの {wmsLink} です。",
- "tileMap.wmsOptions.wmsFormatLabel": "WMS フォーマット",
- "tileMap.wmsOptions.wmsLayersLabel": "WMS レイヤー",
- "tileMap.wmsOptions.wmsLinkText": "OGC スタンダード",
- "tileMap.wmsOptions.wmsMapServerLabel": "WMS マップサーバー",
- "tileMap.wmsOptions.wmsServerSupportedStylesListTip": "WMS サーバーがサポートしている使用スタイルのコンマ区切りのリストです。大抵は空白のままです。",
- "tileMap.wmsOptions.wmsStylesLabel": "WMS スタイル",
- "tileMap.wmsOptions.wmsUrlLabel": "WMS URL",
- "tileMap.wmsOptions.wmsVersionLabel": "WMS バージョン",
"timelion.badge.readOnly.text": "読み込み専用",
"timelion.badge.readOnly.tooltip": "Timelion シートを保存できません",
"timelion.breadcrumbs.create": "作成",
@@ -8325,13 +8305,6 @@
"xpack.ingestManager.agentListStatus.offlineLabel": "オフライン",
"xpack.ingestManager.agentListStatus.onlineLabel": "オンライン",
"xpack.ingestManager.agentListStatus.totalLabel": "エージェント",
- "xpack.ingestManager.apiKeysForm.configLabel": "構成",
- "xpack.ingestManager.apiKeysForm.nameLabel": "キー名",
- "xpack.ingestManager.apiKeysForm.saveButton": "保存",
- "xpack.ingestManager.apiKeysList.apiKeyColumnTitle": "API キー",
- "xpack.ingestManager.apiKeysList.configColumnTitle": "構成",
- "xpack.ingestManager.apiKeysList.emptyEnrollmentKeysMessage": "API キーがありません",
- "xpack.ingestManager.apiKeysList.nameColumnTitle": "名前",
"xpack.ingestManager.appNavigation.configurationsLinkText": "構成",
"xpack.ingestManager.appNavigation.fleetLinkText": "フリート",
"xpack.ingestManager.appNavigation.overviewLinkText": "概要",
@@ -8410,8 +8383,6 @@
"xpack.ingestManager.deleteAgentConfigs.successMultipleNotificationTitle": "{count} 件のエージェント構成を削除しました",
"xpack.ingestManager.deleteAgentConfigs.successSingleNotificationTitle": "エージェント構成「{id}」を削除しました",
"xpack.ingestManager.deleteApiKeys.confirmModal.cancelButtonLabel": "キャンセル",
- "xpack.ingestManager.deleteApiKeys.confirmModal.confirmButtonLabel": "削除",
- "xpack.ingestManager.deleteApiKeys.confirmModal.title": "API キーを削除: {apiKeyId}",
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsMessage": "{agentConfigName} が一部のエージェントで既に使用されていることをフリートが検出しました。",
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsTitle": "このアクションは {agentsCount} {agentsCount, plural, one {# エージェント} other {# エージェント}}に影響します",
"xpack.ingestManager.deleteDatasource.confirmModal.cancelButtonLabel": "キャンセル",
@@ -8434,9 +8405,7 @@
"xpack.ingestManager.editConfig.successNotificationTitle": "エージェント構成「{name}」を更新しました",
"xpack.ingestManager.enrollmentApiKeyForm.namePlaceholder": "名前を選択",
"xpack.ingestManager.enrollmentApiKeyList.createNewButton": "新規キーを作成",
- "xpack.ingestManager.enrollmentApiKeyList.hideTableButton": "を非表示",
"xpack.ingestManager.enrollmentApiKeyList.useExistingsButton": "既存のキーを使用",
- "xpack.ingestManager.enrollmentApiKeyList.viewTableButton": "表示",
"xpack.ingestManager.epm.addDatasourceButtonText": "データソースを作成",
"xpack.ingestManager.epm.pageSubtitle": "人気のアプリやサービスのパッケージを参照する",
"xpack.ingestManager.epm.pageTitle": "Elastic Package Manager",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 60b958623bdc76..819112feb9f572 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -2580,7 +2580,6 @@
"telemetry.welcomeBanner.enableButtonLabel": "启用",
"telemetry.welcomeBanner.telemetryConfigDetailsDescription.telemetryPrivacyStatementLinkText": "遥测隐私声明",
"telemetry.welcomeBanner.title": "帮助我们改进 Elastic Stack",
- "tileMap.baseMapsVisualization.childShouldImplementMethodErrorMessage": "子函数应实现此方法以响应数据更新",
"tileMap.function.help": "磁贴地图可视化",
"tileMap.geohashLayer.mapTitle": "{mapType} 地图类型无法识别",
"tileMap.tooltipFormatter.latitudeLabel": "纬度",
@@ -2602,25 +2601,6 @@
"tileMap.visParams.desaturateTilesLabel": "降低平铺地图饱和度",
"tileMap.visParams.mapTypeLabel": "地图类型",
"tileMap.visParams.reduceVibrancyOfTileColorsTip": "降低平铺地图颜色的亮度。此设置在任何版本的 IE 浏览器中均不起作用。",
- "tileMap.wmsOptions.attributionStringTip": "右下角的属性字符串。",
- "tileMap.wmsOptions.baseLayerSettingsTitle": "基础图层设置",
- "tileMap.wmsOptions.imageFormatToUseTip": "通常为 image/png 或 image/jpeg。如果服务器返回透明图层,则使用 png。",
- "tileMap.wmsOptions.layersLabel": "图层",
- "tileMap.wmsOptions.listOfLayersToUseTip": "要使用的图层逗号分隔列表。",
- "tileMap.wmsOptions.mapLoadFailDescription": "如果此参数不正确,将无法加载地图。",
- "tileMap.wmsOptions.urlOfWMSWebServiceTip": "WMS Web 服务的 URL。",
- "tileMap.wmsOptions.useWMSCompliantMapTileServerTip": "使用符合 WMS 规范的平铺地图服务器。仅适用于高级用户。",
- "tileMap.wmsOptions.versionOfWMSserverSupportsTip": "服务器支持的 WMS 版本。",
- "tileMap.wmsOptions.wmsAttributionLabel": "WMS 属性",
- "tileMap.wmsOptions.wmsDescription": "WMS 是用于地图图像服务的 {wmsLink}。",
- "tileMap.wmsOptions.wmsFormatLabel": "WMS 格式",
- "tileMap.wmsOptions.wmsLayersLabel": "WMS 图层",
- "tileMap.wmsOptions.wmsLinkText": "OGC 标准",
- "tileMap.wmsOptions.wmsMapServerLabel": "WMS 地图服务器",
- "tileMap.wmsOptions.wmsServerSupportedStylesListTip": "要使用的以逗号分隔的 WMS 服务器支持的样式列表。在大部分情况下为空。",
- "tileMap.wmsOptions.wmsStylesLabel": "WMS 样式",
- "tileMap.wmsOptions.wmsUrlLabel": "WMS url",
- "tileMap.wmsOptions.wmsVersionLabel": "WMS 版本",
"timelion.badge.readOnly.text": "只读",
"timelion.badge.readOnly.tooltip": "无法保存 Timelion 工作表",
"timelion.breadcrumbs.create": "创建",
@@ -8328,13 +8308,6 @@
"xpack.ingestManager.agentListStatus.offlineLabel": "脱机",
"xpack.ingestManager.agentListStatus.onlineLabel": "联机",
"xpack.ingestManager.agentListStatus.totalLabel": "代理",
- "xpack.ingestManager.apiKeysForm.configLabel": "配置",
- "xpack.ingestManager.apiKeysForm.nameLabel": "密钥名称",
- "xpack.ingestManager.apiKeysForm.saveButton": "保存",
- "xpack.ingestManager.apiKeysList.apiKeyColumnTitle": "API 密钥",
- "xpack.ingestManager.apiKeysList.configColumnTitle": "配置",
- "xpack.ingestManager.apiKeysList.emptyEnrollmentKeysMessage": "无 API 密钥",
- "xpack.ingestManager.apiKeysList.nameColumnTitle": "名称",
"xpack.ingestManager.appNavigation.configurationsLinkText": "配置",
"xpack.ingestManager.appNavigation.fleetLinkText": "Fleet",
"xpack.ingestManager.appNavigation.overviewLinkText": "概览",
@@ -8413,8 +8386,6 @@
"xpack.ingestManager.deleteAgentConfigs.successMultipleNotificationTitle": "已删除 {count} 个代理配置",
"xpack.ingestManager.deleteAgentConfigs.successSingleNotificationTitle": "已删除代理配置“{id}”",
"xpack.ingestManager.deleteApiKeys.confirmModal.cancelButtonLabel": "取消",
- "xpack.ingestManager.deleteApiKeys.confirmModal.confirmButtonLabel": "删除",
- "xpack.ingestManager.deleteApiKeys.confirmModal.title": "删除 api 密钥:{apiKeyId}",
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsMessage": "Fleet 已检测到 {agentConfigName} 已由您的部分代理使用。",
"xpack.ingestManager.deleteDatasource.confirmModal.affectedAgentsTitle": "此操作将影响 {agentsCount} 个 {agentsCount, plural, one {代理} other {代理}}。",
"xpack.ingestManager.deleteDatasource.confirmModal.cancelButtonLabel": "取消",
@@ -8437,9 +8408,7 @@
"xpack.ingestManager.editConfig.successNotificationTitle": "代理配置“{name}”已更新",
"xpack.ingestManager.enrollmentApiKeyForm.namePlaceholder": "选择名称",
"xpack.ingestManager.enrollmentApiKeyList.createNewButton": "创建新密钥",
- "xpack.ingestManager.enrollmentApiKeyList.hideTableButton": "隐藏",
"xpack.ingestManager.enrollmentApiKeyList.useExistingsButton": "使用现有密钥",
- "xpack.ingestManager.enrollmentApiKeyList.viewTableButton": "查看",
"xpack.ingestManager.epm.addDatasourceButtonText": "创建数据源",
"xpack.ingestManager.epm.pageSubtitle": "浏览热门应用和服务的软件。",
"xpack.ingestManager.epm.pageTitle": "Elastic Package Manager",
diff --git a/x-pack/plugins/upgrade_assistant/server/plugin.ts b/x-pack/plugins/upgrade_assistant/server/plugin.ts
index bdca506cc73385..0cdf1ca05feac2 100644
--- a/x-pack/plugins/upgrade_assistant/server/plugin.ts
+++ b/x-pack/plugins/upgrade_assistant/server/plugin.ts
@@ -25,6 +25,8 @@ import { registerClusterCheckupRoutes } from './routes/cluster_checkup';
import { registerDeprecationLoggingRoutes } from './routes/deprecation_logging';
import { registerReindexIndicesRoutes, createReindexWorker } from './routes/reindex_indices';
import { registerTelemetryRoutes } from './routes/telemetry';
+import { telemetrySavedObjectType, reindexOperationSavedObjectType } from './saved_object_types';
+
import { RouteDependencies } from './types';
interface PluginsSetup {
@@ -57,11 +59,14 @@ export class UpgradeAssistantServerPlugin implements Plugin {
}
setup(
- { http, getStartServices, capabilities }: CoreSetup,
+ { http, getStartServices, capabilities, savedObjects }: CoreSetup,
{ usageCollection, cloud, licensing }: PluginsSetup
) {
this.licensing = licensing;
+ savedObjects.registerType(reindexOperationSavedObjectType);
+ savedObjects.registerType(telemetrySavedObjectType);
+
const router = http.createRouter();
const dependencies: RouteDependencies = {
@@ -85,8 +90,12 @@ export class UpgradeAssistantServerPlugin implements Plugin {
registerTelemetryRoutes(dependencies);
if (usageCollection) {
- getStartServices().then(([{ savedObjects, elasticsearch }]) => {
- registerUpgradeAssistantUsageCollector({ elasticsearch, usageCollection, savedObjects });
+ getStartServices().then(([{ savedObjects: savedObjectsService, elasticsearch }]) => {
+ registerUpgradeAssistantUsageCollector({
+ elasticsearch,
+ usageCollection,
+ savedObjects: savedObjectsService,
+ });
});
}
}
diff --git a/x-pack/legacy/plugins/index_management/index.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts
similarity index 52%
rename from x-pack/legacy/plugins/index_management/index.ts
rename to x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts
index afca15203b9702..dee0a74d8994bb 100644
--- a/x-pack/legacy/plugins/index_management/index.ts
+++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/index.ts
@@ -4,10 +4,5 @@
* you may not use this file except in compliance with the Elastic License.
*/
-// TODO: Remove this once CCR is migrated to the plugins directory.
-export function indexManagement(kibana: any) {
- return new kibana.Plugin({
- id: 'index_management',
- configPrefix: 'xpack.index_management',
- });
-}
+export { reindexOperationSavedObjectType } from './reindex_operation_saved_object_type';
+export { telemetrySavedObjectType } from './telemetry_saved_object_type';
diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/reindex_operation_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/reindex_operation_saved_object_type.ts
new file mode 100644
index 00000000000000..ba661fbeceb267
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/reindex_operation_saved_object_type.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { SavedObjectsType } from 'src/core/server';
+
+import { REINDEX_OP_TYPE } from '../../common/types';
+
+export const reindexOperationSavedObjectType: SavedObjectsType = {
+ name: REINDEX_OP_TYPE,
+ hidden: false,
+ namespaceType: 'agnostic',
+ mappings: {
+ properties: {
+ reindexTaskId: {
+ type: 'keyword',
+ },
+ indexName: {
+ type: 'keyword',
+ },
+ newIndexName: {
+ type: 'keyword',
+ },
+ status: {
+ type: 'integer',
+ },
+ locked: {
+ type: 'date',
+ },
+ lastCompletedStep: {
+ type: 'integer',
+ },
+ errorMessage: {
+ type: 'keyword',
+ },
+ reindexTaskPercComplete: {
+ type: 'float',
+ },
+ runningReindexCount: {
+ type: 'integer',
+ },
+ reindexOptions: {
+ properties: {
+ openAndClose: {
+ type: 'boolean',
+ },
+ queueSettings: {
+ properties: {
+ queuedAt: {
+ type: 'long',
+ },
+ startedAt: {
+ type: 'long',
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts b/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts
new file mode 100644
index 00000000000000..b1321e634c0f1c
--- /dev/null
+++ b/x-pack/plugins/upgrade_assistant/server/saved_object_types/telemetry_saved_object_type.ts
@@ -0,0 +1,67 @@
+/*
+ * 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 { SavedObjectsType } from 'src/core/server';
+
+import { UPGRADE_ASSISTANT_TYPE } from '../../common/types';
+
+export const telemetrySavedObjectType: SavedObjectsType = {
+ name: UPGRADE_ASSISTANT_TYPE,
+ hidden: false,
+ namespaceType: 'agnostic',
+ mappings: {
+ properties: {
+ ui_open: {
+ properties: {
+ overview: {
+ type: 'long',
+ null_value: 0,
+ },
+ cluster: {
+ type: 'long',
+ null_value: 0,
+ },
+ indices: {
+ type: 'long',
+ null_value: 0,
+ },
+ },
+ },
+ ui_reindex: {
+ properties: {
+ close: {
+ type: 'long',
+ null_value: 0,
+ },
+ open: {
+ type: 'long',
+ null_value: 0,
+ },
+ start: {
+ type: 'long',
+ null_value: 0,
+ },
+ stop: {
+ type: 'long',
+ null_value: 0,
+ },
+ },
+ },
+ features: {
+ properties: {
+ deprecation_logging: {
+ properties: {
+ enabled: {
+ type: 'boolean',
+ null_value: true,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+};
diff --git a/x-pack/test/api_integration/apis/security/session.ts b/x-pack/test/api_integration/apis/security/session.ts
index ef7e48388ff660..fcdf268ff27b0a 100644
--- a/x-pack/test/api_integration/apis/security/session.ts
+++ b/x-pack/test/api_integration/apis/security/session.ts
@@ -56,7 +56,7 @@ export default function({ getService }: FtrProviderContext) {
expect(body.now).to.be.a('number');
expect(body.idleTimeoutExpiration).to.be.a('number');
expect(body.lifespanExpiration).to.be(null);
- expect(body.provider).to.be('basic');
+ expect(body.provider).to.eql({ type: 'basic', name: 'basic' });
});
it('should not extend the session', async () => {
diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts
index d664357c3ba126..e5b840b335846f 100644
--- a/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts
+++ b/x-pack/test/plugin_api_integration/test_suites/event_log/public_api_integration.ts
@@ -7,7 +7,7 @@
import { merge, omit, times, chunk, isEmpty } from 'lodash';
import uuid from 'uuid';
import expect from '@kbn/expect/expect.js';
-import moment, { Moment } from 'moment';
+import moment from 'moment';
import { FtrProviderContext } from '../../ftr_provider_context';
import { IEvent } from '../../../../plugins/event_log/server';
import { IValidatedEvent } from '../../../../plugins/event_log/server/types';
@@ -43,10 +43,8 @@ export default function({ getService }: FtrProviderContext) {
it('should support pagination for events', async () => {
const id = uuid.v4();
- const timestamp = moment();
- const [firstExpectedEvent, ...expectedEvents] = times(6, () =>
- fakeEvent(id, fakeEventTiming(timestamp.add(1, 's')))
- );
+ const [firstExpectedEvent, ...expectedEvents] = times(6, () => fakeEvent(id));
+
// run one first to create the SO and avoid clashes
await logTestEvent(id, firstExpectedEvent);
await Promise.all(expectedEvents.map(event => logTestEvent(id, event)));
@@ -82,10 +80,7 @@ export default function({ getService }: FtrProviderContext) {
it('should support sorting by event end', async () => {
const id = uuid.v4();
- const timestamp = moment();
- const [firstExpectedEvent, ...expectedEvents] = times(6, () =>
- fakeEvent(id, fakeEventTiming(timestamp.add(1, 's')))
- );
+ const [firstExpectedEvent, ...expectedEvents] = times(6, () => fakeEvent(id));
// run one first to create the SO and avoid clashes
await logTestEvent(id, firstExpectedEvent);
await Promise.all(expectedEvents.map(event => logTestEvent(id, event)));
@@ -106,21 +101,24 @@ export default function({ getService }: FtrProviderContext) {
it('should support date ranges for events', async () => {
const id = uuid.v4();
- const timestamp = moment();
-
- const firstEvent = fakeEvent(id, fakeEventTiming(timestamp));
+ // write a document that shouldn't be found in the inclusive date range search
+ const firstEvent = fakeEvent(id);
await logTestEvent(id, firstEvent);
- await delay(100);
- const start = timestamp.add(1, 's').toISOString();
+ // wait a second, get the start time for the date range search
+ await delay(1000);
+ const start = new Date().toISOString();
- const expectedEvents = times(6, () => fakeEvent(id, fakeEventTiming(timestamp.add(1, 's'))));
+ // write the documents that we should be found in the date range searches
+ const expectedEvents = times(6, () => fakeEvent(id));
await Promise.all(expectedEvents.map(event => logTestEvent(id, event)));
- const end = timestamp.add(1, 's').toISOString();
+ // get the end time for the date range search
+ const end = new Date().toISOString();
- await delay(100);
- const lastEvent = fakeEvent(id, fakeEventTiming(timestamp.add(1, 's')));
+ // write a document that shouldn't be found in the inclusive date range search
+ await delay(1000);
+ const lastEvent = fakeEvent(id);
await logTestEvent(id, lastEvent);
await retry.try(async () => {
@@ -195,29 +193,12 @@ export default function({ getService }: FtrProviderContext) {
.expect(200);
}
- function fakeEventTiming(start: Moment): Partial {
- return {
- event: {
- start: start.toISOString(),
- end: start
- .clone()
- .add(500, 'milliseconds')
- .toISOString(),
- },
- };
- }
-
function fakeEvent(id: string, overrides: Partial = {}): IEvent {
- const start = moment().toISOString();
- const end = moment().toISOString();
return merge(
{
event: {
provider: 'event_log_fixture',
action: 'test',
- start,
- end,
- duration: 1000000,
},
kibana: {
saved_objects: [
diff --git a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts
index 2de395308ce74c..31668e8345275e 100644
--- a/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts
+++ b/x-pack/test/plugin_api_integration/test_suites/event_log/service_api_integration.ts
@@ -3,6 +3,7 @@
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
+import uuid from 'uuid';
import expect from '@kbn/expect/expect.js';
import { IEvent } from '../../../../plugins/event_log/server';
import { FtrProviderContext } from '../../ftr_provider_context';
@@ -97,7 +98,7 @@ export default function({ getService }: FtrProviderContext) {
await registerProviderActions('provider4', ['action1', 'action2']);
}
- const eventId = '1';
+ const eventId = uuid.v4();
const event: IEvent = {
event: { action: 'action1', provider: 'provider4' },
kibana: { saved_objects: [{ type: 'event_log_test', id: eventId }] },