diff --git a/.eslintrc.js b/.eslintrc.js index 7e865cd99a6d3..f45088f046bdd 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1595,7 +1595,6 @@ module.exports = { */ { files: [ - 'src/plugins/security_oss/**/*.{js,mjs,ts,tsx}', 'src/plugins/interactive_setup/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/encrypted_saved_objects/**/*.{js,mjs,ts,tsx}', 'x-pack/plugins/security/**/*.{js,mjs,ts,tsx}', diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 244689025173f..59c238ec058a5 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -287,9 +287,7 @@ # Security /src/core/server/csp/ @elastic/kibana-security @elastic/kibana-core -/src/plugins/security_oss/ @elastic/kibana-security /src/plugins/interactive_setup/ @elastic/kibana-security -/test/security_functional/ @elastic/kibana-security /x-pack/plugins/spaces/ @elastic/kibana-security /x-pack/plugins/encrypted_saved_objects/ @elastic/kibana-security /x-pack/plugins/security/ @elastic/kibana-security diff --git a/.i18nrc.json b/.i18nrc.json index 46d2f8c6a23bf..707c1cef2594b 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -55,7 +55,6 @@ "newsfeed": "src/plugins/newsfeed", "savedObjects": "src/plugins/saved_objects", "savedObjectsManagement": "src/plugins/saved_objects_management", - "security": "src/plugins/security_oss", "server": "src/legacy/server", "statusPage": "src/legacy/core_plugins/status_page", "telemetry": ["src/plugins/telemetry", "src/plugins/telemetry_management_section"], diff --git a/api_docs/security_oss.json b/api_docs/security_oss.json deleted file mode 100644 index 601563752d47d..0000000000000 --- a/api_docs/security_oss.json +++ /dev/null @@ -1,203 +0,0 @@ -{ - "id": "securityOss", - "client": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [], - "setup": { - "parentPluginId": "securityOss", - "id": "def-public.SecurityOssPluginSetup", - "type": "Interface", - "tags": [], - "label": "SecurityOssPluginSetup", - "description": [], - "path": "src/plugins/security_oss/public/plugin.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "securityOss", - "id": "def-public.SecurityOssPluginSetup.insecureCluster", - "type": "Object", - "tags": [], - "label": "insecureCluster", - "description": [], - "signature": [ - "InsecureClusterServiceSetup" - ], - "path": "src/plugins/security_oss/public/plugin.ts", - "deprecated": false - } - ], - "lifecycle": "setup", - "initialIsOpen": true - }, - "start": { - "parentPluginId": "securityOss", - "id": "def-public.SecurityOssPluginStart", - "type": "Interface", - "tags": [], - "label": "SecurityOssPluginStart", - "description": [], - "path": "src/plugins/security_oss/public/plugin.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "securityOss", - "id": "def-public.SecurityOssPluginStart.insecureCluster", - "type": "Object", - "tags": [], - "label": "insecureCluster", - "description": [], - "signature": [ - "InsecureClusterServiceStart" - ], - "path": "src/plugins/security_oss/public/plugin.ts", - "deprecated": false - }, - { - "parentPluginId": "securityOss", - "id": "def-public.SecurityOssPluginStart.anonymousAccess", - "type": "Object", - "tags": [], - "label": "anonymousAccess", - "description": [], - "signature": [ - "{ getAccessURLParameters: () => Promise | null>; getCapabilities: () => Promise<", - "Capabilities", - ">; }" - ], - "path": "src/plugins/security_oss/public/plugin.ts", - "deprecated": false - } - ], - "lifecycle": "start", - "initialIsOpen": true - } - }, - "server": { - "classes": [], - "functions": [], - "interfaces": [], - "enums": [], - "misc": [], - "objects": [], - "setup": { - "parentPluginId": "securityOss", - "id": "def-server.SecurityOssPluginSetup", - "type": "Interface", - "tags": [], - "label": "SecurityOssPluginSetup", - "description": [], - "path": "src/plugins/security_oss/server/plugin.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "securityOss", - "id": "def-server.SecurityOssPluginSetup.showInsecureClusterWarning$", - "type": "Object", - "tags": [], - "label": "showInsecureClusterWarning$", - "description": [ - "\nAllows consumers to show/hide the insecure cluster warning." - ], - "signature": [ - "BehaviorSubject", - "" - ], - "path": "src/plugins/security_oss/server/plugin.ts", - "deprecated": false - }, - { - "parentPluginId": "securityOss", - "id": "def-server.SecurityOssPluginSetup.setAnonymousAccessServiceProvider", - "type": "Function", - "tags": [], - "label": "setAnonymousAccessServiceProvider", - "description": [ - "\nSet the provider function that returns a service to deal with the anonymous access." - ], - "signature": [ - "(provider: () => ", - "AnonymousAccessService", - ") => void" - ], - "path": "src/plugins/security_oss/server/plugin.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "securityOss", - "id": "def-server.SecurityOssPluginSetup.setAnonymousAccessServiceProvider.$1", - "type": "Function", - "tags": [], - "label": "provider", - "description": [], - "signature": [ - "() => ", - "AnonymousAccessService" - ], - "path": "src/plugins/security_oss/server/plugin.ts", - "deprecated": false, - "isRequired": true - } - ], - "returnComment": [] - } - ], - "lifecycle": "setup", - "initialIsOpen": true - } - }, - "common": { - "classes": [], - "functions": [], - "interfaces": [ - { - "parentPluginId": "securityOss", - "id": "def-common.AppState", - "type": "Interface", - "tags": [], - "label": "AppState", - "description": [ - "\nDefines Security OSS application state." - ], - "path": "src/plugins/security_oss/common/app_state.ts", - "deprecated": false, - "children": [ - { - "parentPluginId": "securityOss", - "id": "def-common.AppState.insecureClusterAlert", - "type": "Object", - "tags": [], - "label": "insecureClusterAlert", - "description": [], - "signature": [ - "{ displayAlert: boolean; }" - ], - "path": "src/plugins/security_oss/common/app_state.ts", - "deprecated": false - }, - { - "parentPluginId": "securityOss", - "id": "def-common.AppState.anonymousAccess", - "type": "Object", - "tags": [], - "label": "anonymousAccess", - "description": [], - "signature": [ - "{ isEnabled: boolean; accessURLParameters: Record | null; }" - ], - "path": "src/plugins/security_oss/common/app_state.ts", - "deprecated": false - } - ], - "initialIsOpen": false - } - ], - "enums": [], - "misc": [], - "objects": [] - } -} \ No newline at end of file diff --git a/api_docs/security_oss.mdx b/api_docs/security_oss.mdx deleted file mode 100644 index 4cd5bc2a76135..0000000000000 --- a/api_docs/security_oss.mdx +++ /dev/null @@ -1,40 +0,0 @@ ---- -id: kibSecurityOssPluginApi -slug: /kibana-dev-docs/api/securityOss -title: "securityOss" -image: https://source.unsplash.com/400x175/?github -summary: API docs for the securityOss plugin -date: 2020-11-16 -tags: ['contributor', 'dev', 'apidocs', 'kibana', 'securityOss'] -warning: This document is auto-generated and is meant to be viewed inside our experimental, new docs system. Reach out in #docs-engineering for more info. ---- -import securityOssObj from './security_oss.json'; - -This plugin exposes a limited set of security functionality to OSS plugins. - -Contact [Platform Security](https://github.com/orgs/elastic/teams/kibana-security) for questions regarding this plugin. - -**Code health stats** - -| Public API count | Any count | Items lacking comments | Missing exports | -|-------------------|-----------|------------------------|-----------------| -| 12 | 0 | 9 | 3 | - -## Client - -### Setup - - -### Start - - -## Server - -### Setup - - -## Common - -### Interfaces - - diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index cbf46801fa86f..f9cbd35d72867 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -223,11 +223,6 @@ oss plugins. |The service exposed by this plugin informs consumers whether they should optimize for non-interactivity. In this way plugins can avoid loading unnecessary code, data or other services. -|{kib-repo}blob/{branch}/src/plugins/security_oss/README.md[securityOss] -|securityOss is responsible for educating users about Elastic's free security features, -so they can properly protect the data within their clusters. - - |{kib-repo}blob/{branch}/src/plugins/share/README.md[share] |The share plugin contains various utilities for displaying sharing context menu, generating deep links to other apps, and creating short URLs. diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index a0ca88e4e04bd..80cd01e05b870 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -64,7 +64,6 @@ pageLoadAssetSize: savedObjectsTaggingOss: 20590 searchprofiler: 67080 security: 95864 - securityOss: 30806 share: 99061 snapshotRestore: 79032 spaces: 57868 diff --git a/scripts/functional_tests.js b/scripts/functional_tests.js index 032a28477bc90..89f20121867dc 100644 --- a/scripts/functional_tests.js +++ b/scripts/functional_tests.js @@ -12,7 +12,6 @@ const alwaysImportedTests = [ require.resolve('../test/plugin_functional/config.ts'), require.resolve('../test/ui_capabilities/newsfeed_err/config.ts'), require.resolve('../test/new_visualize_flow/config.ts'), - require.resolve('../test/security_functional/config.ts'), ]; // eslint-disable-next-line no-restricted-syntax const onlyNotInCoverageTests = [ diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker index dd5b66af9ef21..f87d1c9664273 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker @@ -366,6 +366,7 @@ kibana_vars=( xpack.security.session.idleTimeout xpack.security.session.lifespan xpack.security.sessionTimeout + xpack.security.showInsecureClusterWarning xpack.securitySolution.alertMergeStrategy xpack.securitySolution.alertIgnoreFields xpack.securitySolution.endpointResultListDefaultFirstPageIndex diff --git a/src/plugins/security_oss/README.md b/src/plugins/security_oss/README.md deleted file mode 100644 index 6143149fec384..0000000000000 --- a/src/plugins/security_oss/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# `securityOss` plugin - -`securityOss` is responsible for educating users about Elastic's free security features, -so they can properly protect the data within their clusters. diff --git a/src/plugins/security_oss/common/app_state.ts b/src/plugins/security_oss/common/app_state.ts deleted file mode 100644 index c6ccbb17377a3..0000000000000 --- a/src/plugins/security_oss/common/app_state.ts +++ /dev/null @@ -1,18 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -/** - * Defines Security OSS application state. - */ -export interface AppState { - insecureClusterAlert: { displayAlert: boolean }; - anonymousAccess: { - isEnabled: boolean; - accessURLParameters: Record | null; - }; -} diff --git a/src/plugins/security_oss/jest.config.js b/src/plugins/security_oss/jest.config.js deleted file mode 100644 index 692d85f69a740..0000000000000 --- a/src/plugins/security_oss/jest.config.js +++ /dev/null @@ -1,16 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -module.exports = { - preset: '@kbn/test', - rootDir: '../../..', - roots: ['/src/plugins/security_oss'], - coverageDirectory: '/target/kibana-coverage/jest/src/plugins/security_oss', - coverageReporters: ['text', 'html'], - collectCoverageFrom: ['/src/plugins/security_oss/{common,public,server}/**/*.{ts,tsx}'], -}; diff --git a/src/plugins/security_oss/kibana.json b/src/plugins/security_oss/kibana.json deleted file mode 100644 index c93b5c3b60714..0000000000000 --- a/src/plugins/security_oss/kibana.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "id": "securityOss", - "owner": { - "name": "Platform Security", - "githubTeam": "kibana-security" - }, - "description": "This plugin exposes a limited set of security functionality to OSS plugins.", - "version": "8.0.0", - "kibanaVersion": "kibana", - "configPath": ["security"], - "ui": true, - "server": true, - "requiredPlugins": [], - "requiredBundles": [] -} diff --git a/src/plugins/security_oss/public/app_state/app_state_service.mock.ts b/src/plugins/security_oss/public/app_state/app_state_service.mock.ts deleted file mode 100644 index bfad596a9be2a..0000000000000 --- a/src/plugins/security_oss/public/app_state/app_state_service.mock.ts +++ /dev/null @@ -1,21 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { AppState } from '../../common'; -import type { AppStateServiceStart } from './app_state_service'; - -export const mockAppStateService = { - createStart: (): jest.Mocked => { - return { getState: jest.fn() }; - }, - createAppState: (appState: Partial = {}) => ({ - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - ...appState, - }), -}; diff --git a/src/plugins/security_oss/public/app_state/app_state_service.test.ts b/src/plugins/security_oss/public/app_state/app_state_service.test.ts deleted file mode 100644 index ec491e0faac2f..0000000000000 --- a/src/plugins/security_oss/public/app_state/app_state_service.test.ts +++ /dev/null @@ -1,62 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { coreMock } from 'src/core/public/mocks'; - -import { AppStateService } from './app_state_service'; - -describe('AppStateService', () => { - describe('#start', () => { - it('returns default state for the anonymous routes', async () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(true); - - const appStateService = new AppStateService(); - await expect(appStateService.start({ core: coreStart }).getState()).resolves.toEqual({ - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - - expect(coreStart.http.get).not.toHaveBeenCalled(); - }); - - it('returns default state if current state cannot be retrieved', async () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - - const failureReason = new Error('Uh oh.'); - coreStart.http.get.mockRejectedValue(failureReason); - - const appStateService = new AppStateService(); - await expect(appStateService.start({ core: coreStart }).getState()).resolves.toEqual({ - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - - expect(coreStart.http.get).toHaveBeenCalledTimes(1); - expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security_oss/app_state'); - }); - - it('returns retrieved state', async () => { - const coreStart = coreMock.createStart(); - coreStart.http.anonymousPaths.isAnonymous.mockReturnValue(false); - - const state = { - insecureClusterAlert: { displayAlert: true }, - anonymousAccess: { isEnabled: true, accessURLParameters: { hint: 'some-hint' } }, - }; - coreStart.http.get.mockResolvedValue(state); - - const appStateService = new AppStateService(); - await expect(appStateService.start({ core: coreStart }).getState()).resolves.toEqual(state); - - expect(coreStart.http.get).toHaveBeenCalledTimes(1); - expect(coreStart.http.get).toHaveBeenCalledWith('/internal/security_oss/app_state'); - }); - }); -}); diff --git a/src/plugins/security_oss/public/app_state/app_state_service.ts b/src/plugins/security_oss/public/app_state/app_state_service.ts deleted file mode 100644 index 8f6e9c0f08e77..0000000000000 --- a/src/plugins/security_oss/public/app_state/app_state_service.ts +++ /dev/null @@ -1,37 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { CoreStart } from 'src/core/public'; - -import type { AppState } from '../../common'; - -const DEFAULT_APP_STATE = Object.freeze({ - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, -}); - -interface StartDeps { - core: Pick; -} - -export interface AppStateServiceStart { - getState: () => Promise; -} - -/** - * Service that allows to retrieve application state. - */ -export class AppStateService { - start({ core }: StartDeps): AppStateServiceStart { - const appStatePromise = core.http.anonymousPaths.isAnonymous(window.location.pathname) - ? Promise.resolve(DEFAULT_APP_STATE) - : core.http.get('/internal/security_oss/app_state').catch(() => DEFAULT_APP_STATE); - - return { getState: () => appStatePromise }; - } -} diff --git a/src/plugins/security_oss/public/app_state/index.ts b/src/plugins/security_oss/public/app_state/index.ts deleted file mode 100644 index 585dc13258301..0000000000000 --- a/src/plugins/security_oss/public/app_state/index.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { AppStateService, AppStateServiceStart } from './app_state_service'; diff --git a/src/plugins/security_oss/public/index.ts b/src/plugins/security_oss/public/index.ts deleted file mode 100644 index 0f67c6ed5442b..0000000000000 --- a/src/plugins/security_oss/public/index.ts +++ /dev/null @@ -1,16 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { PluginInitializerContext } from 'src/core/public'; - -import { SecurityOssPlugin } from './plugin'; - -export { SecurityOssPluginSetup, SecurityOssPluginStart } from './plugin'; - -export const plugin = (initializerContext: PluginInitializerContext) => - new SecurityOssPlugin(initializerContext); diff --git a/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.test.tsx b/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.test.tsx deleted file mode 100644 index 7664b69540c50..0000000000000 --- a/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.test.tsx +++ /dev/null @@ -1,26 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { defaultAlertText } from './default_alert'; - -describe('defaultAlertText', () => { - it('creates a valid MountPoint that can cleanup correctly', () => { - const mountPoint = defaultAlertText(jest.fn()); - - const el = document.createElement('div'); - const unmount = mountPoint(el); - - expect(el.querySelectorAll('[data-test-subj="insecureClusterDefaultAlertText"]')).toHaveLength( - 1 - ); - - unmount(); - - expect(el).toMatchInlineSnapshot(`
`); - }); -}); diff --git a/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.tsx b/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.tsx deleted file mode 100644 index 192be5188041b..0000000000000 --- a/src/plugins/security_oss/public/insecure_cluster_service/components/default_alert.tsx +++ /dev/null @@ -1,86 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { - EuiButton, - EuiCheckbox, - EuiFlexGroup, - EuiFlexItem, - EuiSpacer, - EuiText, -} from '@elastic/eui'; -import React, { useState } from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; - -import { i18n } from '@kbn/i18n'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; -import type { MountPoint } from 'src/core/public'; - -export const defaultAlertTitle = i18n.translate('security.checkup.insecureClusterTitle', { - defaultMessage: 'Your data is not secure', -}); - -export const defaultAlertText: (onDismiss: (persist: boolean) => void) => MountPoint = - (onDismiss) => (e) => { - const AlertText = () => { - const [persist, setPersist] = useState(false); - - return ( - -
- - - - - setPersist(changeEvent.target.checked)} - label={i18n.translate('security.checkup.dontShowAgain', { - defaultMessage: `Don't show again`, - })} - /> - - - - - {i18n.translate('security.checkup.learnMoreButtonText', { - defaultMessage: `Learn more`, - })} - - - - onDismiss(persist)} - data-test-subj="defaultDismissAlertButton" - > - {i18n.translate('security.checkup.dismissButtonText', { - defaultMessage: `Dismiss`, - })} - - - -
-
- ); - }; - - render(, e); - - return () => unmountComponentAtNode(e); - }; diff --git a/src/plugins/security_oss/public/insecure_cluster_service/components/index.ts b/src/plugins/security_oss/public/insecure_cluster_service/components/index.ts deleted file mode 100644 index 8405fa3d43681..0000000000000 --- a/src/plugins/security_oss/public/insecure_cluster_service/components/index.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { defaultAlertTitle, defaultAlertText } from './default_alert'; diff --git a/src/plugins/security_oss/public/insecure_cluster_service/index.ts b/src/plugins/security_oss/public/insecure_cluster_service/index.ts deleted file mode 100644 index 4f087ad56d715..0000000000000 --- a/src/plugins/security_oss/public/insecure_cluster_service/index.ts +++ /dev/null @@ -1,13 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { - InsecureClusterService, - InsecureClusterServiceSetup, - InsecureClusterServiceStart, -} from './insecure_cluster_service'; diff --git a/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.mock.tsx b/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.mock.tsx deleted file mode 100644 index accf597aafa0b..0000000000000 --- a/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.mock.tsx +++ /dev/null @@ -1,26 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { - InsecureClusterServiceSetup, - InsecureClusterServiceStart, -} from './insecure_cluster_service'; - -export const mockInsecureClusterService = { - createSetup: () => { - return { - setAlertTitle: jest.fn(), - setAlertText: jest.fn(), - } as InsecureClusterServiceSetup; - }, - createStart: () => { - return { - hideAlert: jest.fn(), - } as InsecureClusterServiceStart; - }, -}; diff --git a/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.test.tsx b/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.test.tsx deleted file mode 100644 index 25eae0b11c519..0000000000000 --- a/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.test.tsx +++ /dev/null @@ -1,342 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { nextTick } from '@kbn/test/jest'; -import { coreMock } from 'src/core/public/mocks'; - -import { mockAppStateService } from '../app_state/app_state_service.mock'; -import type { ConfigType } from '../config'; -import { InsecureClusterService } from './insecure_cluster_service'; - -let mockOnDismissCallback: (persist: boolean) => void = jest.fn().mockImplementation(() => { - throw new Error('expected callback to be replaced!'); -}); - -jest.mock('./components', () => { - return { - defaultAlertTitle: 'mocked default alert title', - defaultAlertText: (onDismiss: any) => { - mockOnDismissCallback = onDismiss; - return 'mocked default alert text'; - }, - }; -}); - -interface InitOpts { - tenant?: string; -} - -function initCore({ tenant = '/server-base-path' }: InitOpts = {}) { - const coreSetup = coreMock.createSetup(); - (coreSetup.http.basePath.serverBasePath as string) = tenant; - - const coreStart = coreMock.createStart(); - coreStart.notifications.toasts.addWarning.mockReturnValue({ id: 'mock_alert_id' }); - return { coreSetup, coreStart }; -} - -describe('InsecureClusterService', () => { - describe('display scenarios', () => { - it('does not display an alert when the warning is explicitly disabled via config', async () => { - const config: ConfigType = { showInsecureClusterWarning: false }; - const { coreSetup, coreStart } = initCore(); - const storage = coreMock.createStorage(); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).not.toHaveBeenCalled(); - expect(coreStart.notifications.toasts.addWarning).not.toHaveBeenCalled(); - - expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); - expect(storage.setItem).not.toHaveBeenCalled(); - }); - - it('does not display an alert when state indicates that alert should not be shown', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - const storage = coreMock.createStorage(); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: false } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning).not.toHaveBeenCalled(); - - expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); - expect(storage.setItem).not.toHaveBeenCalled(); - }); - - it('only reads storage information from the current tenant', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore({ tenant: '/my-specific-tenant' }); - - const storage = coreMock.createStorage(); - storage.getItem.mockReturnValue(JSON.stringify({ show: false })); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(storage.getItem).toHaveBeenCalledTimes(1); - expect(storage.getItem).toHaveBeenCalledWith( - 'insecureClusterWarningVisibility/my-specific-tenant' - ); - }); - - it('does not display an alert when hidden via storage', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - - const storage = coreMock.createStorage(); - storage.getItem.mockReturnValue(JSON.stringify({ show: false })); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).not.toHaveBeenCalled(); - expect(coreStart.notifications.toasts.addWarning).not.toHaveBeenCalled(); - - expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); - expect(storage.setItem).not.toHaveBeenCalled(); - }); - - it('displays an alert when persisted preference is corrupted', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - - const storage = coreMock.createStorage(); - storage.getItem.mockReturnValue('{ this is a string of invalid JSON'); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); - - expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); - expect(storage.setItem).not.toHaveBeenCalled(); - }); - - it('displays an alert when enabled via config and endpoint checks', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - const storage = coreMock.createStorage(); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "iconType": "alert", - "text": "mocked default alert text", - "title": "mocked default alert title", - }, - Object { - "toastLifeTimeMs": 864000000, - }, - ] - `); - - expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); - expect(storage.setItem).not.toHaveBeenCalled(); - }); - - it('dismisses the alert when requested, and remembers this preference', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - const storage = coreMock.createStorage(); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); - - mockOnDismissCallback(true); - - expect(coreStart.notifications.toasts.remove).toHaveBeenCalledTimes(1); - expect(storage.setItem).toHaveBeenCalledWith( - 'insecureClusterWarningVisibility/server-base-path', - JSON.stringify({ show: false }) - ); - }); - }); - - describe('#setup', () => { - it('allows the alert title and text to be replaced exactly once', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const storage = coreMock.createStorage(); - - const { coreSetup } = initCore(); - - const service = new InsecureClusterService(config, storage); - const { setAlertTitle, setAlertText } = service.setup({ core: coreSetup }); - setAlertTitle('some new title'); - setAlertText('some new alert text'); - - expect(() => setAlertTitle('')).toThrowErrorMatchingInlineSnapshot( - `"alert title has already been set"` - ); - expect(() => setAlertText('')).toThrowErrorMatchingInlineSnapshot( - `"alert text has already been set"` - ); - }); - - it('allows the alert title and text to be replaced', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - const storage = coreMock.createStorage(); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - const { setAlertTitle, setAlertText } = service.setup({ core: coreSetup }); - setAlertTitle('some new title'); - setAlertText('some new alert text'); - - service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "iconType": "alert", - "text": "some new alert text", - "title": "some new title", - }, - Object { - "toastLifeTimeMs": 864000000, - }, - ] - `); - - expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); - expect(storage.setItem).not.toHaveBeenCalled(); - }); - }); - - describe('#start', () => { - it('allows the alert to be hidden via start contract, and remembers this preference', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - const storage = coreMock.createStorage(); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - const { hideAlert } = service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); - - hideAlert(true); - - expect(coreStart.notifications.toasts.remove).toHaveBeenCalledTimes(1); - expect(storage.setItem).toHaveBeenCalledWith( - 'insecureClusterWarningVisibility/server-base-path', - JSON.stringify({ show: false }) - ); - }); - - it('allows the alert to be hidden via start contract, and does not remember the preference', async () => { - const config: ConfigType = { showInsecureClusterWarning: true }; - const { coreSetup, coreStart } = initCore(); - const storage = coreMock.createStorage(); - - const appState = mockAppStateService.createStart(); - appState.getState.mockResolvedValue( - mockAppStateService.createAppState({ insecureClusterAlert: { displayAlert: true } }) - ); - - const service = new InsecureClusterService(config, storage); - service.setup({ core: coreSetup }); - const { hideAlert } = service.start({ core: coreStart, appState }); - - await nextTick(); - - expect(appState.getState).toHaveBeenCalledTimes(1); - expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); - - hideAlert(false); - - expect(coreStart.notifications.toasts.remove).toHaveBeenCalledTimes(1); - expect(storage.setItem).not.toHaveBeenCalled(); - }); - }); -}); diff --git a/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.tsx b/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.tsx deleted file mode 100644 index 6cb2079cbe954..0000000000000 --- a/src/plugins/security_oss/public/insecure_cluster_service/insecure_cluster_service.tsx +++ /dev/null @@ -1,149 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { BehaviorSubject, combineLatest, from } from 'rxjs'; -import { distinctUntilChanged, map } from 'rxjs/operators'; - -import type { CoreSetup, CoreStart, MountPoint, Toast } from 'src/core/public'; - -import type { AppStateServiceStart } from '../app_state'; -import type { ConfigType } from '../config'; -import { defaultAlertText, defaultAlertTitle } from './components'; - -interface SetupDeps { - core: Pick; -} - -interface StartDeps { - core: Pick; - appState: AppStateServiceStart; -} - -export interface InsecureClusterServiceSetup { - setAlertTitle: (alertTitle: string | MountPoint) => void; - setAlertText: (alertText: string | MountPoint) => void; -} - -export interface InsecureClusterServiceStart { - hideAlert: (persist: boolean) => void; -} - -export class InsecureClusterService { - private enabled: boolean; - - private alertVisibility$: BehaviorSubject; - - private storage: Storage; - - private alertToast?: Toast; - - private alertTitle?: string | MountPoint; - - private alertText?: string | MountPoint; - - private storageKey?: string; - - constructor(config: Pick, storage: Storage) { - this.storage = storage; - this.enabled = config.showInsecureClusterWarning; - this.alertVisibility$ = new BehaviorSubject(this.enabled); - } - - public setup({ core }: SetupDeps): InsecureClusterServiceSetup { - const tenant = core.http.basePath.serverBasePath; - this.storageKey = `insecureClusterWarningVisibility${tenant}`; - this.enabled = this.enabled && this.getPersistedVisibilityPreference(); - this.alertVisibility$.next(this.enabled); - - return { - setAlertTitle: (alertTitle: string | MountPoint) => { - if (this.alertTitle) { - throw new Error('alert title has already been set'); - } - this.alertTitle = alertTitle; - }, - setAlertText: (alertText: string | MountPoint) => { - if (this.alertText) { - throw new Error('alert text has already been set'); - } - this.alertText = alertText; - }, - }; - } - - public start({ core, appState }: StartDeps): InsecureClusterServiceStart { - if (this.enabled) { - this.initializeAlert(core, appState); - } - - return { - hideAlert: (persist: boolean) => this.setAlertVisibility(false, persist), - }; - } - - private initializeAlert(core: StartDeps['core'], appState: AppStateServiceStart) { - const appState$ = from(appState.getState()); - - // 10 days is reasonably long enough to call "forever" for a page load. - // Can't go too much longer than this. See https://github.com/elastic/kibana/issues/64264#issuecomment-618400354 - const oneMinute = 60000; - const tenDays = oneMinute * 60 * 24 * 10; - - combineLatest([appState$, this.alertVisibility$]) - .pipe( - map( - ([{ insecureClusterAlert }, isAlertVisible]) => - insecureClusterAlert.displayAlert && isAlertVisible - ), - distinctUntilChanged() - ) - .subscribe((showAlert) => { - if (showAlert && !this.alertToast) { - this.alertToast = core.notifications.toasts.addWarning( - { - title: this.alertTitle ?? defaultAlertTitle, - text: - this.alertText ?? - defaultAlertText((persist: boolean) => this.setAlertVisibility(false, persist)), - iconType: 'alert', - }, - { - toastLifeTimeMs: tenDays, - } - ); - } else if (!showAlert && this.alertToast) { - core.notifications.toasts.remove(this.alertToast); - this.alertToast = undefined; - } - }); - } - - private setAlertVisibility(show: boolean, persist: boolean) { - if (!this.enabled) { - return; - } - this.alertVisibility$.next(show); - if (persist) { - this.setPersistedVisibilityPreference(show); - } - } - - private getPersistedVisibilityPreference() { - const entry = this.storage.getItem(this.storageKey!) ?? '{}'; - try { - const { show = true } = JSON.parse(entry); - return show; - } catch (e) { - return true; - } - } - - private setPersistedVisibilityPreference(show: boolean) { - this.storage.setItem(this.storageKey!, JSON.stringify({ show })); - } -} diff --git a/src/plugins/security_oss/public/mocks.ts b/src/plugins/security_oss/public/mocks.ts deleted file mode 100644 index ee15f57b351cd..0000000000000 --- a/src/plugins/security_oss/public/mocks.ts +++ /dev/null @@ -1,9 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { mockSecurityOssPlugin } from './plugin.mock'; diff --git a/src/plugins/security_oss/public/plugin.mock.ts b/src/plugins/security_oss/public/plugin.mock.ts deleted file mode 100644 index 23a6050a0e501..0000000000000 --- a/src/plugins/security_oss/public/plugin.mock.ts +++ /dev/null @@ -1,31 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; - -import type { InsecureClusterServiceStart } from './insecure_cluster_service'; -import { mockInsecureClusterService } from './insecure_cluster_service/insecure_cluster_service.mock'; -import type { SecurityOssPluginSetup, SecurityOssPluginStart } from './plugin'; - -export const mockSecurityOssPlugin = { - createSetup: () => { - return { - insecureCluster: mockInsecureClusterService.createSetup(), - } as DeeplyMockedKeys; - }, - createStart: () => { - return { - insecureCluster: - mockInsecureClusterService.createStart() as jest.Mocked, - anonymousAccess: { - getAccessURLParameters: jest.fn().mockResolvedValue(null), - getCapabilities: jest.fn().mockResolvedValue({}), - }, - } as DeeplyMockedKeys; - }, -}; diff --git a/src/plugins/security_oss/public/plugin.ts b/src/plugins/security_oss/public/plugin.ts deleted file mode 100644 index c26323b01f356..0000000000000 --- a/src/plugins/security_oss/public/plugin.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { - Capabilities, - CoreSetup, - CoreStart, - Plugin, - PluginInitializerContext, -} from 'src/core/public'; - -import { AppStateService } from './app_state'; -import type { ConfigType } from './config'; -import type { - InsecureClusterServiceSetup, - InsecureClusterServiceStart, -} from './insecure_cluster_service'; -import { InsecureClusterService } from './insecure_cluster_service'; - -export interface SecurityOssPluginSetup { - insecureCluster: InsecureClusterServiceSetup; -} - -export interface SecurityOssPluginStart { - insecureCluster: InsecureClusterServiceStart; - anonymousAccess: { - getAccessURLParameters: () => Promise | null>; - getCapabilities: () => Promise; - }; -} - -export class SecurityOssPlugin - implements Plugin -{ - private readonly config = this.initializerContext.config.get(); - private readonly insecureClusterService = new InsecureClusterService(this.config, localStorage); - private readonly appStateService = new AppStateService(); - - constructor(private readonly initializerContext: PluginInitializerContext) {} - - public setup(core: CoreSetup) { - return { - insecureCluster: this.insecureClusterService.setup({ core }), - }; - } - - public start(core: CoreStart) { - const appState = this.appStateService.start({ core }); - return { - insecureCluster: this.insecureClusterService.start({ core, appState }), - anonymousAccess: { - async getAccessURLParameters() { - const { anonymousAccess } = await appState.getState(); - return anonymousAccess.accessURLParameters; - }, - getCapabilities() { - return core.http.get( - '/internal/security_oss/anonymous_access/capabilities' - ); - }, - }, - }; - } -} diff --git a/src/plugins/security_oss/server/config.ts b/src/plugins/security_oss/server/config.ts deleted file mode 100644 index 4be4bf96c2d82..0000000000000 --- a/src/plugins/security_oss/server/config.ts +++ /dev/null @@ -1,16 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { TypeOf } from '@kbn/config-schema'; -import { schema } from '@kbn/config-schema'; - -export type ConfigType = TypeOf; - -export const ConfigSchema = schema.object({ - showInsecureClusterWarning: schema.boolean({ defaultValue: true }), -}); diff --git a/src/plugins/security_oss/server/index.ts b/src/plugins/security_oss/server/index.ts deleted file mode 100644 index 42736302b5d26..0000000000000 --- a/src/plugins/security_oss/server/index.ts +++ /dev/null @@ -1,24 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { TypeOf } from '@kbn/config-schema'; -import type { PluginConfigDescriptor, PluginInitializerContext } from 'src/core/server'; - -import { ConfigSchema } from './config'; -import { SecurityOssPlugin } from './plugin'; - -export { SecurityOssPluginSetup } from './plugin'; - -export const config: PluginConfigDescriptor> = { - schema: ConfigSchema, - exposeToBrowser: { - showInsecureClusterWarning: true, - }, -}; - -export const plugin = (context: PluginInitializerContext) => new SecurityOssPlugin(context); diff --git a/src/plugins/security_oss/server/plugin.test.ts b/src/plugins/security_oss/server/plugin.test.ts deleted file mode 100644 index 5858fabd6a706..0000000000000 --- a/src/plugins/security_oss/server/plugin.test.ts +++ /dev/null @@ -1,28 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { coreMock } from 'src/core/server/mocks'; - -import { SecurityOssPlugin } from './plugin'; - -describe('SecurityOss Plugin', () => { - describe('#setup', () => { - it('exposes the proper contract', async () => { - const context = coreMock.createPluginInitializerContext(); - const plugin = new SecurityOssPlugin(context); - const core = coreMock.createSetup(); - const contract = plugin.setup(core); - expect(Object.keys(contract)).toMatchInlineSnapshot(` - Array [ - "showInsecureClusterWarning$", - "setAnonymousAccessServiceProvider", - ] - `); - }); - }); -}); diff --git a/src/plugins/security_oss/server/plugin.ts b/src/plugins/security_oss/server/plugin.ts deleted file mode 100644 index 6adea272ed490..0000000000000 --- a/src/plugins/security_oss/server/plugin.ts +++ /dev/null @@ -1,100 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { Observable } from 'rxjs'; -import { BehaviorSubject } from 'rxjs'; - -import type { - Capabilities, - CoreSetup, - KibanaRequest, - Logger, - Plugin, - PluginInitializerContext, -} from 'src/core/server'; - -import { createClusterDataCheck } from './check_cluster_data'; -import type { ConfigType } from './config'; -import { setupAnonymousAccessCapabilitiesRoute, setupAppStateRoute } from './routes'; - -export interface SecurityOssPluginSetup { - /** - * Allows consumers to show/hide the insecure cluster warning. - */ - showInsecureClusterWarning$: BehaviorSubject; - - /** - * Set the provider function that returns a service to deal with the anonymous access. - * @param provider - */ - setAnonymousAccessServiceProvider: (provider: () => AnonymousAccessService) => void; -} - -export interface AnonymousAccessService { - /** - * Indicates whether anonymous access is enabled. - */ - readonly isAnonymousAccessEnabled: boolean; - - /** - * A map of query string parameters that should be specified in the URL pointing to Kibana so - * that anonymous user can automatically log in. - */ - readonly accessURLParameters: Readonly> | null; - - /** - * Gets capabilities of the anonymous service account. - * @param request Kibana request instance. - */ - getCapabilities: (request: KibanaRequest) => Promise; -} - -export class SecurityOssPlugin implements Plugin { - private readonly config$: Observable; - private readonly logger: Logger; - private anonymousAccessServiceProvider?: () => AnonymousAccessService; - - constructor(initializerContext: PluginInitializerContext) { - this.config$ = initializerContext.config.create(); - this.logger = initializerContext.logger.get(); - } - - public setup(core: CoreSetup) { - const router = core.http.createRouter(); - const showInsecureClusterWarning$ = new BehaviorSubject(true); - - setupAppStateRoute({ - router, - log: this.logger, - config$: this.config$, - displayModifier$: showInsecureClusterWarning$, - doesClusterHaveUserData: createClusterDataCheck(), - getAnonymousAccessService: () => this.anonymousAccessServiceProvider?.() ?? null, - }); - - setupAnonymousAccessCapabilitiesRoute({ - router, - getAnonymousAccessService: () => this.anonymousAccessServiceProvider?.() ?? null, - }); - - return { - showInsecureClusterWarning$, - setAnonymousAccessServiceProvider: (provider: () => AnonymousAccessService) => { - if (this.anonymousAccessServiceProvider) { - throw new Error('Anonymous Access service provider is already set.'); - } - - this.anonymousAccessServiceProvider = provider; - }, - }; - } - - public start() {} - - public stop() {} -} diff --git a/src/plugins/security_oss/server/routes/anonymous_access_capabilities.ts b/src/plugins/security_oss/server/routes/anonymous_access_capabilities.ts deleted file mode 100644 index 80273b41063ad..0000000000000 --- a/src/plugins/security_oss/server/routes/anonymous_access_capabilities.ts +++ /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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { IRouter } from 'src/core/server'; - -import type { AnonymousAccessService } from '../plugin'; - -interface Deps { - router: IRouter; - getAnonymousAccessService: () => AnonymousAccessService | null; -} - -/** - * Defines route that returns capabilities of the anonymous service account. - */ -export function setupAnonymousAccessCapabilitiesRoute({ router, getAnonymousAccessService }: Deps) { - router.get( - { path: '/internal/security_oss/anonymous_access/capabilities', validate: false }, - async (_context, request, response) => { - const anonymousAccessService = getAnonymousAccessService(); - if (!anonymousAccessService) { - return response.custom({ statusCode: 501, body: 'Not Implemented' }); - } - - return response.ok({ body: await anonymousAccessService.getCapabilities(request) }); - } - ); -} diff --git a/src/plugins/security_oss/server/routes/app_state.ts b/src/plugins/security_oss/server/routes/app_state.ts deleted file mode 100644 index 6ecac362559d1..0000000000000 --- a/src/plugins/security_oss/server/routes/app_state.ts +++ /dev/null @@ -1,66 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import type { Observable } from 'rxjs'; -import { combineLatest } from 'rxjs'; - -import type { IRouter, Logger } from 'src/core/server'; - -import type { AppState } from '../../common'; -import type { createClusterDataCheck } from '../check_cluster_data'; -import type { ConfigType } from '../config'; -import type { AnonymousAccessService } from '../plugin'; - -interface Deps { - router: IRouter; - log: Logger; - config$: Observable; - displayModifier$: Observable; - doesClusterHaveUserData: ReturnType; - getAnonymousAccessService: () => AnonymousAccessService | null; -} - -export const setupAppStateRoute = ({ - router, - log, - config$, - displayModifier$, - doesClusterHaveUserData, - getAnonymousAccessService, -}: Deps) => { - let showInsecureClusterWarning = false; - - combineLatest([config$, displayModifier$]).subscribe(([config, displayModifier]) => { - showInsecureClusterWarning = config.showInsecureClusterWarning && displayModifier; - }); - - router.get( - { path: '/internal/security_oss/app_state', validate: false }, - async (context, request, response) => { - let displayAlert = false; - if (showInsecureClusterWarning) { - displayAlert = await doesClusterHaveUserData( - context.core.elasticsearch.client.asInternalUser, - log - ); - } - - const anonymousAccessService = getAnonymousAccessService(); - const appState: AppState = { - insecureClusterAlert: { displayAlert }, - anonymousAccess: { - isEnabled: anonymousAccessService?.isAnonymousAccessEnabled ?? false, - accessURLParameters: anonymousAccessService?.accessURLParameters - ? Object.fromEntries(anonymousAccessService.accessURLParameters.entries()) - : null, - }, - }; - return response.ok({ body: appState }); - } - ); -}; diff --git a/src/plugins/security_oss/server/routes/index.ts b/src/plugins/security_oss/server/routes/index.ts deleted file mode 100644 index bde267dd44f8c..0000000000000 --- a/src/plugins/security_oss/server/routes/index.ts +++ /dev/null @@ -1,10 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -export { setupAppStateRoute } from './app_state'; -export { setupAnonymousAccessCapabilitiesRoute } from './anonymous_access_capabilities'; diff --git a/src/plugins/security_oss/server/routes/integration_tests/anonymous_access_capabilities.test.ts b/src/plugins/security_oss/server/routes/integration_tests/anonymous_access_capabilities.test.ts deleted file mode 100644 index 0dba0433a3625..0000000000000 --- a/src/plugins/security_oss/server/routes/integration_tests/anonymous_access_capabilities.test.ts +++ /dev/null @@ -1,77 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import supertest from 'supertest'; - -import type { UnwrapPromise } from '@kbn/utility-types'; -import { setupServer } from 'src/core/server/test_utils'; - -import type { AnonymousAccessService } from '../../plugin'; -import { setupAnonymousAccessCapabilitiesRoute } from '../anonymous_access_capabilities'; - -type SetupServerReturn = UnwrapPromise>; -const pluginId = Symbol('securityOss'); - -interface SetupOpts { - getAnonymousAccessService?: () => AnonymousAccessService | null; -} - -describe('GET /internal/security_oss/anonymous_access/capabilities', () => { - let server: SetupServerReturn['server']; - let httpSetup: SetupServerReturn['httpSetup']; - - const setupTestServer = async ({ getAnonymousAccessService = () => null }: SetupOpts = {}) => { - ({ server, httpSetup } = await setupServer(pluginId)); - - const router = httpSetup.createRouter('/'); - - setupAnonymousAccessCapabilitiesRoute({ router, getAnonymousAccessService }); - - await server.start(); - }; - - afterEach(async () => { - await server.stop(); - }); - - it('responds with 501 if anonymous access service is provided', async () => { - await setupTestServer(); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/anonymous_access/capabilities') - .expect(501, { - statusCode: 501, - error: 'Not Implemented', - message: 'Not Implemented', - }); - }); - - it('returns anonymous access state if anonymous access service is provided', async () => { - await setupTestServer({ - getAnonymousAccessService: () => ({ - isAnonymousAccessEnabled: true, - accessURLParameters: new Map([['auth_provider_hint', 'anonymous1']]), - getCapabilities: jest.fn().mockResolvedValue({ - navLinks: {}, - management: {}, - catalogue: {}, - custom: { something: true }, - }), - }), - }); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/anonymous_access/capabilities') - .expect(200, { - navLinks: {}, - management: {}, - catalogue: {}, - custom: { something: true }, - }); - }); -}); diff --git a/src/plugins/security_oss/server/routes/integration_tests/app_state.test.ts b/src/plugins/security_oss/server/routes/integration_tests/app_state.test.ts deleted file mode 100644 index 82e7098760e18..0000000000000 --- a/src/plugins/security_oss/server/routes/integration_tests/app_state.test.ts +++ /dev/null @@ -1,184 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { BehaviorSubject, of } from 'rxjs'; -import supertest from 'supertest'; - -import type { UnwrapPromise } from '@kbn/utility-types'; -import { loggingSystemMock } from 'src/core/server/mocks'; -import { setupServer } from 'src/core/server/test_utils'; - -import type { createClusterDataCheck } from '../../check_cluster_data'; -import type { ConfigType } from '../../config'; -import type { AnonymousAccessService } from '../../plugin'; -import { setupAppStateRoute } from '../app_state'; - -type SetupServerReturn = UnwrapPromise>; -const pluginId = Symbol('securityOss'); - -interface SetupOpts { - config?: ConfigType; - displayModifier$?: BehaviorSubject; - doesClusterHaveUserData?: ReturnType; - getAnonymousAccessService?: () => AnonymousAccessService | null; -} - -describe('GET /internal/security_oss/app_state', () => { - let server: SetupServerReturn['server']; - let httpSetup: SetupServerReturn['httpSetup']; - - const setupTestServer = async ({ - config = { showInsecureClusterWarning: true }, - displayModifier$ = new BehaviorSubject(true), - doesClusterHaveUserData = jest.fn().mockResolvedValue(true), - getAnonymousAccessService = () => null, - }: SetupOpts) => { - ({ server, httpSetup } = await setupServer(pluginId)); - - const router = httpSetup.createRouter('/'); - const log = loggingSystemMock.createLogger(); - - setupAppStateRoute({ - router, - log, - config$: of(config), - displayModifier$, - doesClusterHaveUserData, - getAnonymousAccessService, - }); - - await server.start(); - - return { - log, - }; - }; - - afterEach(async () => { - await server.stop(); - }); - - it('responds `insecureClusterAlert.displayAlert == false` if plugin is not configured to display alerts', async () => { - await setupTestServer({ - config: { showInsecureClusterWarning: false }, - }); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - }); - - it('responds `insecureClusterAlert.displayAlert == false` if cluster does not contain user data', async () => { - await setupTestServer({ - config: { showInsecureClusterWarning: true }, - doesClusterHaveUserData: jest.fn().mockResolvedValue(false), - }); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - }); - - it('responds `insecureClusterAlert.displayAlert == false` if displayModifier$ is set to false', async () => { - await setupTestServer({ - config: { showInsecureClusterWarning: true }, - doesClusterHaveUserData: jest.fn().mockResolvedValue(true), - displayModifier$: new BehaviorSubject(false), - }); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - }); - - it('responds `insecureClusterAlert.displayAlert == true` if cluster contains user data', async () => { - await setupTestServer({ - config: { showInsecureClusterWarning: true }, - doesClusterHaveUserData: jest.fn().mockResolvedValue(true), - }); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: true }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - }); - - it('responds to changing displayModifier$ values', async () => { - const displayModifier$ = new BehaviorSubject(true); - - await setupTestServer({ - config: { showInsecureClusterWarning: true }, - doesClusterHaveUserData: jest.fn().mockResolvedValue(true), - displayModifier$, - }); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: true }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - - displayModifier$.next(false); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { isEnabled: false, accessURLParameters: null }, - }); - }); - - it('returns anonymous access state if anonymous access service is provided', async () => { - const displayModifier$ = new BehaviorSubject(true); - - await setupTestServer({ - config: { showInsecureClusterWarning: true }, - doesClusterHaveUserData: jest.fn().mockResolvedValue(true), - displayModifier$, - getAnonymousAccessService: () => ({ - isAnonymousAccessEnabled: true, - accessURLParameters: new Map([['auth_provider_hint', 'anonymous1']]), - getCapabilities: jest.fn(), - }), - }); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: true }, - anonymousAccess: { - isEnabled: true, - accessURLParameters: { auth_provider_hint: 'anonymous1' }, - }, - }); - - displayModifier$.next(false); - - await supertest(httpSetup.server.listener) - .get('/internal/security_oss/app_state') - .expect(200, { - insecureClusterAlert: { displayAlert: false }, - anonymousAccess: { - isEnabled: true, - accessURLParameters: { auth_provider_hint: 'anonymous1' }, - }, - }); - }); -}); diff --git a/src/plugins/security_oss/tsconfig.json b/src/plugins/security_oss/tsconfig.json deleted file mode 100644 index 6ebeff836f69b..0000000000000 --- a/src/plugins/security_oss/tsconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "extends": "../../../tsconfig.base.json", - "compilerOptions": { - "outDir": "./target/types", - "emitDeclarationOnly": true, - "declaration": true, - "declarationMap": true - }, - "include": ["common/**/*", "public/**/*", "server/**/*"], - "references": [{ "path": "../../core/tsconfig.json" }] -} diff --git a/src/plugins/security_oss/public/config.ts b/src/plugins/share/common/anonymous_access/index.mock.ts similarity index 60% rename from src/plugins/security_oss/public/config.ts rename to src/plugins/share/common/anonymous_access/index.mock.ts index 7b0e201bd5658..9b5a8500dff5a 100644 --- a/src/plugins/security_oss/public/config.ts +++ b/src/plugins/share/common/anonymous_access/index.mock.ts @@ -6,6 +6,11 @@ * Side Public License, v 1. */ -export interface ConfigType { - showInsecureClusterWarning: boolean; -} +import type { AnonymousAccessServiceContract } from './types'; + +export const anonymousAccessMock = { + create: (): jest.Mocked => ({ + getState: jest.fn(), + getCapabilities: jest.fn(), + }), +}; diff --git a/src/plugins/security_oss/common/index.ts b/src/plugins/share/common/anonymous_access/index.ts similarity index 80% rename from src/plugins/security_oss/common/index.ts rename to src/plugins/share/common/anonymous_access/index.ts index f02bc941d19e2..d9746fdd247b5 100644 --- a/src/plugins/security_oss/common/index.ts +++ b/src/plugins/share/common/anonymous_access/index.ts @@ -6,4 +6,4 @@ * Side Public License, v 1. */ -export type { AppState } from './app_state'; +export type { AnonymousAccessServiceContract, AnonymousAccessState } from './types'; diff --git a/src/plugins/share/common/anonymous_access/types.ts b/src/plugins/share/common/anonymous_access/types.ts new file mode 100644 index 0000000000000..7ab731dfabaaa --- /dev/null +++ b/src/plugins/share/common/anonymous_access/types.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +import type { Capabilities } from 'src/core/public'; + +/** + * The contract that is used to check anonymous access for the purposes of sharing public links. The implementation is intended to be + * provided by the security plugin. + */ +export interface AnonymousAccessServiceContract { + /** + * This function returns the current state of anonymous access. + */ + getState: () => Promise; + /** + * This function returns the capabilities of the anonymous access user. + */ + getCapabilities: () => Promise; +} + +/** + * The state of anonymous access. + */ +export interface AnonymousAccessState { + /** + * Whether anonymous access is enabled or not. + */ + isEnabled: boolean; + /** + * If anonymous access is enabled, this reflects what URL parameters need to be added to a Kibana link to make it publicly accessible. + * Note that if anonymous access is the only authentication method, this will be null. + */ + accessURLParameters: Record | null; +} diff --git a/src/plugins/share/common/index.ts b/src/plugins/share/common/index.ts index 69898f3d72019..992ec2447855f 100644 --- a/src/plugins/share/common/index.ts +++ b/src/plugins/share/common/index.ts @@ -7,3 +7,4 @@ */ export { LocatorDefinition, LocatorPublic, useLocatorUrl, formatSearchParams } from './url_service'; +export type { AnonymousAccessServiceContract, AnonymousAccessState } from './anonymous_access'; diff --git a/src/plugins/share/kibana.json b/src/plugins/share/kibana.json index 2616b299da28d..2e34da1da0287 100644 --- a/src/plugins/share/kibana.json +++ b/src/plugins/share/kibana.json @@ -9,5 +9,5 @@ }, "description": "Adds URL Service and sharing capabilities to Kibana", "requiredBundles": ["kibanaUtils"], - "optionalPlugins": ["securityOss"] + "optionalPlugins": [] } diff --git a/src/plugins/share/public/components/share_context_menu.tsx b/src/plugins/share/public/components/share_context_menu.tsx index 201bc0c665dd3..8e931c5c579fa 100644 --- a/src/plugins/share/public/components/share_context_menu.tsx +++ b/src/plugins/share/public/components/share_context_menu.tsx @@ -17,7 +17,7 @@ import type { Capabilities } from 'src/core/public'; import { UrlPanelContent } from './url_panel_content'; import { ShareMenuItem, ShareContextMenuPanelItem, UrlParamExtension } from '../types'; -import type { SecurityOssPluginStart } from '../../../security_oss/public'; +import { AnonymousAccessServiceContract } from '../../common/anonymous_access'; interface Props { allowEmbed: boolean; @@ -31,7 +31,7 @@ interface Props { basePath: string; post: HttpStart['post']; embedUrlParamExtensions?: UrlParamExtension[]; - anonymousAccess?: SecurityOssPluginStart['anonymousAccess']; + anonymousAccess?: AnonymousAccessServiceContract; showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean; } diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index 80c29f10b57d5..4fdf6f9b83092 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -32,7 +32,10 @@ import type { Capabilities } from 'src/core/public'; import { shortenUrl } from '../lib/url_shortener'; import { UrlParamExtension } from '../types'; -import type { SecurityOssPluginStart } from '../../../security_oss/public'; +import { + AnonymousAccessServiceContract, + AnonymousAccessState, +} from '../../common/anonymous_access'; interface Props { allowShortUrl: boolean; @@ -43,7 +46,7 @@ interface Props { basePath: string; post: HttpStart['post']; urlParamExtensions?: UrlParamExtension[]; - anonymousAccess?: SecurityOssPluginStart['anonymousAccess']; + anonymousAccess?: AnonymousAccessServiceContract; showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean; } @@ -66,7 +69,7 @@ interface State { url?: string; shortUrlErrorMsg?: string; urlParams?: UrlParams; - anonymousAccessParameters: Record | null; + anonymousAccessParameters: AnonymousAccessState['accessURLParameters']; showPublicUrlSwitch: boolean; } @@ -104,8 +107,8 @@ export class UrlPanelContent extends Component { if (this.props.anonymousAccess) { (async () => { - const anonymousAccessParameters = - await this.props.anonymousAccess!.getAccessURLParameters(); + const { accessURLParameters: anonymousAccessParameters } = + await this.props.anonymousAccess!.getState(); if (!this.mounted) { return; diff --git a/src/plugins/share/public/mocks.ts b/src/plugins/share/public/mocks.ts index 4b8a3b915d13d..73df7257290f0 100644 --- a/src/plugins/share/public/mocks.ts +++ b/src/plugins/share/public/mocks.ts @@ -44,6 +44,7 @@ const createSetupContract = (): Setup => { }, url, navigate: jest.fn(), + setAnonymousAccessServiceProvider: jest.fn(), }; return setupContract; }; diff --git a/src/plugins/share/public/plugin.test.ts b/src/plugins/share/public/plugin.test.ts index c08d13d70d1e2..7d5da45f768d5 100644 --- a/src/plugins/share/public/plugin.test.ts +++ b/src/plugins/share/public/plugin.test.ts @@ -10,7 +10,7 @@ import { registryMock, managerMock } from './plugin.test.mocks'; import { SharePlugin } from './plugin'; import { CoreStart } from 'kibana/public'; import { coreMock } from '../../../core/public/mocks'; -import { mockSecurityOssPlugin } from '../../security_oss/public/mocks'; +import { anonymousAccessMock } from '../common/anonymous_access/index.mock'; describe('SharePlugin', () => { beforeEach(() => { @@ -22,12 +22,8 @@ describe('SharePlugin', () => { describe('setup', () => { test('wires up and returns registry', async () => { const coreSetup = coreMock.createSetup(); - const plugins = { - securityOss: mockSecurityOssPlugin.createSetup(), - }; const setup = await new SharePlugin(coreMock.createPluginInitializerContext()).setup( - coreSetup, - plugins + coreSetup ); expect(registryMock.setup).toHaveBeenCalledWith(); expect(setup.register).toBeDefined(); @@ -35,10 +31,7 @@ describe('SharePlugin', () => { test('registers redirect app', async () => { const coreSetup = coreMock.createSetup(); - const plugins = { - securityOss: mockSecurityOssPlugin.createSetup(), - }; - await new SharePlugin(coreMock.createPluginInitializerContext()).setup(coreSetup, plugins); + await new SharePlugin(coreMock.createPluginInitializerContext()).setup(coreSetup); expect(coreSetup.application.register).toHaveBeenCalledWith( expect.objectContaining({ id: 'short_url_redirect', @@ -50,22 +43,34 @@ describe('SharePlugin', () => { describe('start', () => { test('wires up and returns show function, but not registry', async () => { const coreSetup = coreMock.createSetup(); - const pluginsSetup = { - securityOss: mockSecurityOssPlugin.createSetup(), - }; const service = new SharePlugin(coreMock.createPluginInitializerContext()); - await service.setup(coreSetup, pluginsSetup); - const pluginsStart = { - securityOss: mockSecurityOssPlugin.createStart(), - }; - const start = await service.start({} as CoreStart, pluginsStart); + await service.setup(coreSetup); + const start = await service.start({} as CoreStart); expect(registryMock.start).toHaveBeenCalled(); expect(managerMock.start).toHaveBeenCalledWith( expect.anything(), expect.objectContaining({ getShareMenuItems: expect.any(Function), }), - expect.anything() + undefined + ); + expect(start.toggleShareContextMenu).toBeDefined(); + }); + + test('passes anonymous access service provider to the share menu manager when it is available', async () => { + const coreSetup = coreMock.createSetup(); + const service = new SharePlugin(coreMock.createPluginInitializerContext()); + const setup = await service.setup(coreSetup); + const anonymousAccessServiceProvider = () => anonymousAccessMock.create(); + setup.setAnonymousAccessServiceProvider(anonymousAccessServiceProvider); + const start = await service.start({} as CoreStart); + expect(registryMock.start).toHaveBeenCalled(); + expect(managerMock.start).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ + getShareMenuItems: expect.any(Function), + }), + anonymousAccessServiceProvider ); expect(start.toggleShareContextMenu).toBeDefined(); }); diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 26b5c7e753a2e..103fbb50bb95f 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -10,7 +10,6 @@ import './index.scss'; import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src/core/public'; import { ShareMenuManager, ShareMenuManagerStart } from './services'; -import type { SecurityOssPluginSetup, SecurityOssPluginStart } from '../../security_oss/public'; import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services'; import { createShortUrlRedirectApp } from './services/short_url_redirect_app'; import { @@ -22,14 +21,7 @@ import { UrlService } from '../common/url_service'; import { RedirectManager } from './url_service'; import type { RedirectOptions } from '../common/url_service/locators/redirect'; import { LegacyShortUrlLocatorDefinition } from '../common/url_service/locators/legacy_short_url_locator'; - -export interface ShareSetupDependencies { - securityOss?: SecurityOssPluginSetup; -} - -export interface ShareStartDependencies { - securityOss?: SecurityOssPluginStart; -} +import { AnonymousAccessServiceContract } from '../common'; /** @public */ export type SharePluginSetup = ShareMenuRegistrySetup & { @@ -50,6 +42,11 @@ export type SharePluginSetup = ShareMenuRegistrySetup & { * the locator, then using the locator to navigate. */ navigate(options: RedirectOptions): void; + + /** + * Sets the provider for the anonymous access service; this is consumed by the Security plugin to avoid a circular dependency. + */ + setAnonymousAccessServiceProvider: (provider: () => AnonymousAccessServiceContract) => void; }; /** @public */ @@ -80,10 +77,11 @@ export class SharePlugin implements Plugin { private redirectManager?: RedirectManager; private url?: UrlService; + private anonymousAccessServiceProvider?: () => AnonymousAccessServiceContract; constructor(private readonly initializerContext: PluginInitializerContext) {} - public setup(core: CoreSetup, plugins: ShareSetupDependencies): SharePluginSetup { + public setup(core: CoreSetup): SharePluginSetup { const { application, http } = core; const { basePath } = http; @@ -138,15 +136,21 @@ export class SharePlugin implements Plugin { urlGenerators: this.urlGeneratorsService.setup(core), url: this.url, navigate: (options: RedirectOptions) => this.redirectManager!.navigate(options), + setAnonymousAccessServiceProvider: (provider: () => AnonymousAccessServiceContract) => { + if (this.anonymousAccessServiceProvider) { + throw new Error('Anonymous Access service provider is already set.'); + } + this.anonymousAccessServiceProvider = provider; + }, }; } - public start(core: CoreStart, plugins: ShareStartDependencies): SharePluginStart { + public start(core: CoreStart): SharePluginStart { return { ...this.shareContextMenu.start( core, this.shareMenuRegistry.start(), - plugins.securityOss?.anonymousAccess + this.anonymousAccessServiceProvider ), urlGenerators: this.urlGeneratorsService.start(core), url: this.url!, diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index 144f65f9011dc..c7251ee4dd414 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -15,7 +15,7 @@ import { CoreStart, HttpStart } from 'kibana/public'; import { ShareContextMenu } from '../components/share_context_menu'; import { ShareMenuItem, ShowShareMenuOptions } from '../types'; import { ShareMenuRegistryStart } from './share_menu_registry'; -import type { SecurityOssPluginStart } from '../../../security_oss/public'; +import { AnonymousAccessServiceContract } from '../../common/anonymous_access'; export class ShareMenuManager { private isOpen = false; @@ -25,7 +25,7 @@ export class ShareMenuManager { start( core: CoreStart, shareRegistry: ShareMenuRegistryStart, - anonymousAccess?: SecurityOssPluginStart['anonymousAccess'] + anonymousAccessServiceProvider?: () => AnonymousAccessServiceContract ) { return { /** @@ -35,6 +35,7 @@ export class ShareMenuManager { */ toggleShareContextMenu: (options: ShowShareMenuOptions) => { const menuItems = shareRegistry.getShareMenuItems({ ...options, onClose: this.onClose }); + const anonymousAccess = anonymousAccessServiceProvider?.(); this.toggleShareContextMenu({ ...options, menuItems, @@ -69,7 +70,7 @@ export class ShareMenuManager { menuItems: ShareMenuItem[]; post: HttpStart['post']; basePath: string; - anonymousAccess?: SecurityOssPluginStart['anonymousAccess']; + anonymousAccess: AnonymousAccessServiceContract | undefined; }) { if (this.isOpen) { this.onClose(); diff --git a/src/plugins/share/tsconfig.json b/src/plugins/share/tsconfig.json index c6afc06bc61c2..1f9c438f03fc4 100644 --- a/src/plugins/share/tsconfig.json +++ b/src/plugins/share/tsconfig.json @@ -10,6 +10,5 @@ "references": [ { "path": "../../core/tsconfig.json" }, { "path": "../kibana_utils/tsconfig.json" }, - { "path": "../security_oss/tsconfig.json" } ] } diff --git a/test/security_functional/config.ts b/test/security_functional/config.ts deleted file mode 100644 index b01f27b4cb7d6..0000000000000 --- a/test/security_functional/config.ts +++ /dev/null @@ -1,40 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import path from 'path'; -import { FtrConfigProviderContext } from '@kbn/test'; - -export default async function ({ readConfigFile }: FtrConfigProviderContext) { - const functionalConfig = await readConfigFile(require.resolve('../functional/config')); - - return { - testFiles: [require.resolve('./index.ts')], - services: functionalConfig.get('services'), - pageObjects: functionalConfig.get('pageObjects'), - servers: functionalConfig.get('servers'), - esTestCluster: functionalConfig.get('esTestCluster'), - apps: {}, - snapshots: { - directory: path.resolve(__dirname, 'snapshots'), - }, - junit: { - reportName: 'Security OSS Functional Tests', - }, - kbnTestServer: { - ...functionalConfig.get('kbnTestServer'), - serverArgs: [ - ...functionalConfig - .get('kbnTestServer.serverArgs') - .filter((arg: string) => !arg.startsWith('--security.showInsecureClusterWarning')), - '--security.showInsecureClusterWarning=true', - // Required to load new platform plugins via `--plugin-path` flag. - '--env.name=development', - ], - }, - }; -} diff --git a/test/security_functional/index.ts b/test/security_functional/index.ts deleted file mode 100644 index 0d067c58b84b0..0000000000000 --- a/test/security_functional/index.ts +++ /dev/null @@ -1,17 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { FtrProviderContext } from '../functional/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ loadTestFile }: FtrProviderContext) { - describe('Security OSS', function () { - this.tags(['skipCloud', 'ciGroup2']); - loadTestFile(require.resolve('./insecure_cluster_warning')); - }); -} diff --git a/test/security_functional/insecure_cluster_warning.ts b/test/security_functional/insecure_cluster_warning.ts deleted file mode 100644 index 238643e573540..0000000000000 --- a/test/security_functional/insecure_cluster_warning.ts +++ /dev/null @@ -1,77 +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 - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -import { FtrProviderContext } from 'test/functional/ftr_provider_context'; - -// eslint-disable-next-line import/no-default-export -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const pageObjects = getPageObjects(['common']); - const testSubjects = getService('testSubjects'); - const browser = getService('browser'); - const esArchiver = getService('esArchiver'); - - describe('Insecure Cluster Warning', () => { - before(async () => { - await pageObjects.common.navigateToApp('home'); - await browser.setLocalStorageItem('insecureClusterWarningVisibility', ''); - // starting without user data - await esArchiver.unload('test/functional/fixtures/es_archiver/hamlet'); - }); - - after(async () => { - await esArchiver.unload('test/functional/fixtures/es_archiver/hamlet'); - }); - - describe('without user data', () => { - before(async () => { - await browser.setLocalStorageItem('insecureClusterWarningVisibility', ''); - await esArchiver.unload('test/functional/fixtures/es_archiver/hamlet'); - await esArchiver.emptyKibanaIndex(); - }); - - it('should not warn when the cluster contains no user data', async () => { - await browser.setLocalStorageItem( - 'insecureClusterWarningVisibility', - JSON.stringify({ show: false }) - ); - await pageObjects.common.navigateToApp('home'); - await testSubjects.missingOrFail('insecureClusterDefaultAlertText'); - }); - }); - - describe('with user data', () => { - before(async () => { - await pageObjects.common.navigateToApp('home'); - await browser.setLocalStorageItem('insecureClusterWarningVisibility', ''); - await esArchiver.load('test/functional/fixtures/es_archiver/hamlet'); - }); - - after(async () => { - await esArchiver.unload('test/functional/fixtures/es_archiver/hamlet'); - }); - - it('should warn about an insecure cluster, and hide when dismissed', async () => { - await pageObjects.common.navigateToApp('home'); - await testSubjects.existOrFail('insecureClusterDefaultAlertText'); - - await testSubjects.click('defaultDismissAlertButton'); - - await testSubjects.missingOrFail('insecureClusterDefaultAlertText'); - }); - - it('should not warn when local storage is configured to hide', async () => { - await browser.setLocalStorageItem( - 'insecureClusterWarningVisibility', - JSON.stringify({ show: false }) - ); - await pageObjects.common.navigateToApp('home'); - await testSubjects.missingOrFail('insecureClusterDefaultAlertText'); - }); - }); - }); -} diff --git a/x-pack/plugins/security/common/types.ts b/x-pack/plugins/security/common/types.ts index e6354841cc9e0..2d38dbdccb414 100644 --- a/x-pack/plugins/security/common/types.ts +++ b/x-pack/plugins/security/common/types.ts @@ -19,3 +19,7 @@ export enum LogoutReason { 'LOGGED_OUT' = 'LOGGED_OUT', 'UNAUTHENTICATED' = 'UNAUTHENTICATED', } + +export interface SecurityCheckupState { + displayAlert: boolean; +} diff --git a/x-pack/plugins/security/kibana.json b/x-pack/plugins/security/kibana.json index a29c01b0f31cc..2eeac40e22f14 100644 --- a/x-pack/plugins/security/kibana.json +++ b/x-pack/plugins/security/kibana.json @@ -8,8 +8,8 @@ "version": "8.0.0", "kibanaVersion": "kibana", "configPath": ["xpack", "security"], - "requiredPlugins": ["data", "features", "licensing", "taskManager", "securityOss"], - "optionalPlugins": ["home", "management", "usageCollection", "spaces"], + "requiredPlugins": ["data", "features", "licensing", "taskManager"], + "optionalPlugins": ["home", "management", "usageCollection", "spaces", "share"], "server": true, "ui": true, "requiredBundles": [ diff --git a/x-pack/plugins/security/public/anonymous_access/anonymous_access_service.ts b/x-pack/plugins/security/public/anonymous_access/anonymous_access_service.ts new file mode 100644 index 0000000000000..7f72fe9eaa0f1 --- /dev/null +++ b/x-pack/plugins/security/public/anonymous_access/anonymous_access_service.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { Capabilities, HttpStart } from 'src/core/public'; + +import type { + AnonymousAccessServiceContract, + AnonymousAccessState, +} from '../../../../../src/plugins/share/common'; +import type { SharePluginSetup } from '../../../../../src/plugins/share/public'; + +const DEFAULT_ANONYMOUS_ACCESS_STATE = Object.freeze({ + isEnabled: false, + accessURLParameters: null, +}); + +interface SetupDeps { + share: Pick; +} + +interface StartDeps { + http: HttpStart; +} + +/** + * Service that allows to retrieve application state. + */ +export class AnonymousAccessService { + private internalService!: AnonymousAccessServiceContract; + + setup({ share }: SetupDeps) { + share.setAnonymousAccessServiceProvider(() => this.internalService); + } + + start({ http }: StartDeps) { + this.internalService = { + getCapabilities: () => + http.get('/internal/security/anonymous_access/capabilities'), + getState: () => + http.anonymousPaths.isAnonymous(window.location.pathname) + ? Promise.resolve(DEFAULT_ANONYMOUS_ACCESS_STATE) + : http + .get('/internal/security/anonymous_access/state') + .catch(() => DEFAULT_ANONYMOUS_ACCESS_STATE), + }; + } +} diff --git a/x-pack/plugins/security/public/anonymous_access/index.ts b/x-pack/plugins/security/public/anonymous_access/index.ts new file mode 100644 index 0000000000000..8cee89d1c13d2 --- /dev/null +++ b/x-pack/plugins/security/public/anonymous_access/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { AnonymousAccessService } from './anonymous_access_service'; diff --git a/x-pack/plugins/security/public/config.ts b/x-pack/plugins/security/public/config.ts index 66dd4bab0a850..a494efd02078c 100644 --- a/x-pack/plugins/security/public/config.ts +++ b/x-pack/plugins/security/public/config.ts @@ -7,4 +7,5 @@ export interface ConfigType { loginAssistanceMessage: string; + showInsecureClusterWarning: boolean; } diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts index b936f8d01cfd5..ac478ff0934db 100644 --- a/x-pack/plugins/security/public/mocks.ts +++ b/x-pack/plugins/security/public/mocks.ts @@ -10,13 +10,11 @@ import type { MockAuthenticatedUserProps } from '../common/model/authenticated_u import { mockAuthenticatedUser } from '../common/model/authenticated_user.mock'; import { authenticationMock } from './authentication/index.mock'; import { navControlServiceMock } from './nav_control/index.mock'; -import { createSessionTimeoutMock } from './session/session_timeout.mock'; import { getUiApiMock } from './ui_api/index.mock'; function createSetupMock() { return { authc: authenticationMock.createSetup(), - sessionTimeout: createSessionTimeoutMock(), license: licenseMock.create(), }; } diff --git a/x-pack/plugins/security/public/plugin.test.tsx b/x-pack/plugins/security/public/plugin.test.tsx index 258b0ef9ec6f5..2bc4932b12a0b 100644 --- a/x-pack/plugins/security/public/plugin.test.tsx +++ b/x-pack/plugins/security/public/plugin.test.tsx @@ -12,7 +12,6 @@ import type { CoreSetup } from 'src/core/public'; import { coreMock } from 'src/core/public/mocks'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import { managementPluginMock } from 'src/plugins/management/public/mocks'; -import { mockSecurityOssPlugin } from 'src/plugins/security_oss/public/mocks'; import type { FeaturesPluginStart } from '../../features/public'; import { licensingMock } from '../../licensing/public/mocks'; @@ -38,7 +37,6 @@ describe('Security Plugin', () => { }) as CoreSetup, { licensing: licensingMock.createSetup(), - securityOss: mockSecurityOssPlugin.createSetup(), } ) ).toEqual({ @@ -64,7 +62,6 @@ describe('Security Plugin', () => { plugin.setup(coreSetupMock as CoreSetup, { licensing: licensingMock.createSetup(), - securityOss: mockSecurityOssPlugin.createSetup(), management: managementSetupMock, }); @@ -90,12 +87,11 @@ describe('Security Plugin', () => { const plugin = new SecurityPlugin(coreMock.createPluginInitializerContext()); plugin.setup( coreMock.createSetup({ basePath: '/some-base-path' }) as CoreSetup, - { licensing: licensingMock.createSetup(), securityOss: mockSecurityOssPlugin.createSetup() } + { licensing: licensingMock.createSetup() } ); expect( plugin.start(coreMock.createStart({ basePath: '/some-base-path' }), { - securityOss: mockSecurityOssPlugin.createStart(), data: {} as DataPublicPluginStart, features: {} as FeaturesPluginStart, }) @@ -131,14 +127,12 @@ describe('Security Plugin', () => { coreMock.createSetup({ basePath: '/some-base-path' }) as CoreSetup, { licensing: licensingMock.createSetup(), - securityOss: mockSecurityOssPlugin.createSetup(), management: managementSetupMock, } ); const coreStart = coreMock.createStart({ basePath: '/some-base-path' }); plugin.start(coreStart, { - securityOss: mockSecurityOssPlugin.createStart(), data: {} as DataPublicPluginStart, features: {} as FeaturesPluginStart, management: managementStartMock, @@ -153,7 +147,7 @@ describe('Security Plugin', () => { const plugin = new SecurityPlugin(coreMock.createPluginInitializerContext()); plugin.setup( coreMock.createSetup({ basePath: '/some-base-path' }) as CoreSetup, - { licensing: licensingMock.createSetup(), securityOss: mockSecurityOssPlugin.createSetup() } + { licensing: licensingMock.createSetup() } ); expect(() => plugin.stop()).not.toThrow(); @@ -164,11 +158,10 @@ describe('Security Plugin', () => { plugin.setup( coreMock.createSetup({ basePath: '/some-base-path' }) as CoreSetup, - { licensing: licensingMock.createSetup(), securityOss: mockSecurityOssPlugin.createSetup() } + { licensing: licensingMock.createSetup() } ); plugin.start(coreMock.createStart({ basePath: '/some-base-path' }), { - securityOss: mockSecurityOssPlugin.createStart(), data: {} as DataPublicPluginStart, features: {} as FeaturesPluginStart, }); diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx index 78144f0717164..043cf0765ff31 100644 --- a/x-pack/plugins/security/public/plugin.tsx +++ b/x-pack/plugins/security/public/plugin.tsx @@ -10,18 +10,16 @@ import type { CoreSetup, CoreStart, Plugin, PluginInitializerContext } from 'src import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { HomePublicPluginSetup } from 'src/plugins/home/public'; import type { ManagementSetup, ManagementStart } from 'src/plugins/management/public'; -import type { - SecurityOssPluginSetup, - SecurityOssPluginStart, -} from 'src/plugins/security_oss/public'; import { FeatureCatalogueCategory } from '../../../../src/plugins/home/public'; +import type { SharePluginSetup, SharePluginStart } from '../../../../src/plugins/share/public'; import type { FeaturesPluginStart } from '../../features/public'; import type { LicensingPluginSetup } from '../../licensing/public'; import type { SpacesPluginStart } from '../../spaces/public'; import { SecurityLicenseService } from '../common/licensing'; import type { SecurityLicense } from '../common/licensing'; import { accountManagementApp } from './account_management'; +import { AnonymousAccessService } from './anonymous_access'; import type { AuthenticationServiceSetup, AuthenticationServiceStart } from './authentication'; import { AuthenticationService } from './authentication'; import type { ConfigType } from './config'; @@ -35,17 +33,17 @@ import { getUiApi } from './ui_api'; export interface PluginSetupDependencies { licensing: LicensingPluginSetup; - securityOss: SecurityOssPluginSetup; home?: HomePublicPluginSetup; management?: ManagementSetup; + share?: SharePluginSetup; } export interface PluginStartDependencies { data: DataPublicPluginStart; features: FeaturesPluginStart; - securityOss: SecurityOssPluginStart; management?: ManagementStart; spaces?: SpacesPluginStart; + share?: SharePluginStart; } export class SecurityPlugin @@ -57,22 +55,21 @@ export class SecurityPlugin PluginStartDependencies > { + private readonly config = this.initializerContext.config.get(); private sessionTimeout!: SessionTimeout; private readonly authenticationService = new AuthenticationService(); private readonly navControlService = new SecurityNavControlService(); private readonly securityLicenseService = new SecurityLicenseService(); private readonly managementService = new ManagementService(); - private readonly securityCheckupService = new SecurityCheckupService(); + private readonly securityCheckupService = new SecurityCheckupService(this.config, localStorage); + private readonly anonymousAccessService = new AnonymousAccessService(); private authc!: AuthenticationServiceSetup; - private readonly config: ConfigType; - constructor(private readonly initializerContext: PluginInitializerContext) { - this.config = this.initializerContext.config.get(); - } + constructor(private readonly initializerContext: PluginInitializerContext) {} public setup( core: CoreSetup, - { home, licensing, management, securityOss }: PluginSetupDependencies + { home, licensing, management, share }: PluginSetupDependencies ): SecurityPluginSetup { const { http, notifications } = core; const { anonymousPaths } = http; @@ -86,7 +83,7 @@ export class SecurityPlugin const { license } = this.securityLicenseService.setup({ license$: licensing.license$ }); - this.securityCheckupService.setup({ securityOssSetup: securityOss }); + this.securityCheckupService.setup({ http: core.http }); this.authc = this.authenticationService.setup({ application: core.application, @@ -135,6 +132,10 @@ export class SecurityPlugin }); } + if (share) { + this.anonymousAccessService.setup({ share }); + } + return { authc: this.authc, license, @@ -143,15 +144,23 @@ export class SecurityPlugin public start( core: CoreStart, - { management, securityOss }: PluginStartDependencies + { management, share }: PluginStartDependencies ): SecurityPluginStart { this.sessionTimeout.start(); - this.securityCheckupService.start({ securityOssStart: securityOss, docLinks: core.docLinks }); + this.securityCheckupService.start({ + http: core.http, + notifications: core.notifications, + docLinks: core.docLinks, + }); if (management) { this.managementService.start({ capabilities: core.application.capabilities }); } + if (share) { + this.anonymousAccessService.start({ http: core.http }); + } + return { uiApi: getUiApi({ core }), navControlService: this.navControlService.start({ core }), @@ -164,7 +173,6 @@ export class SecurityPlugin this.navControlService.stop(); this.securityLicenseService.stop(); this.managementService.stop(); - this.securityCheckupService.stop(); } } diff --git a/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx b/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx index eeeac4533ed12..86ab93574211c 100644 --- a/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx +++ b/x-pack/plugins/security/public/security_checkup/components/insecure_cluster_alert.tsx @@ -26,15 +26,13 @@ export const insecureClusterAlertTitle = i18n.translate( ); export const insecureClusterAlertText = ( - getDocLinks: () => DocLinksStart, + docLinks: DocLinksStart, onDismiss: (persist: boolean) => void ) => ((e) => { const AlertText = () => { const [persist, setPersist] = useState(false); - const enableSecurityDocLink = `${ - getDocLinks().links.security.elasticsearchEnableSecurity - }?blade=kibanasecuritymessage`; + const enableSecurityDocLink = `${docLinks.links.security.elasticsearchEnableSecurity}?blade=kibanasecuritymessage`; return ( diff --git a/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts b/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts index c96b1e888ff9c..1c4a15a8cfb93 100644 --- a/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts +++ b/x-pack/plugins/security/public/security_checkup/security_checkup_service.test.ts @@ -5,75 +5,176 @@ * 2.0. */ -import type { MountPoint } from 'src/core/public'; -import { docLinksServiceMock } from 'src/core/public/mocks'; -import { mockSecurityOssPlugin } from 'src/plugins/security_oss/public/mocks'; +import { nextTick } from '@kbn/test/jest'; +import type { DocLinksStart } from 'src/core/public'; +import { coreMock } from 'src/core/public/mocks'; -import { insecureClusterAlertTitle } from './components'; +import type { ConfigType } from '../config'; import { SecurityCheckupService } from './security_checkup_service'; -let mockOnDismiss = jest.fn(); +let mockOnDismissCallback: (persist: boolean) => void = jest.fn().mockImplementation(() => { + throw new Error('expected callback to be replaced!'); +}); jest.mock('./components', () => { return { insecureClusterAlertTitle: 'mock insecure cluster title', - insecureClusterAlertText: (getDocLinksService: any, onDismiss: any) => { - mockOnDismiss = onDismiss; - const { insecureClusterAlertText } = jest.requireActual( - './components/insecure_cluster_alert' - ); - return insecureClusterAlertText(getDocLinksService, onDismiss); + insecureClusterAlertText: ( + _getDocLinks: () => DocLinksStart, + onDismiss: (persist: boolean) => void + ) => { + mockOnDismissCallback = onDismiss; + return 'mock insecure cluster text'; }, }; }); +interface TestParams { + showInsecureClusterWarning: boolean; + displayAlert: boolean; + tenant?: string; + storageValue?: string; +} + +async function setupAndStart({ + showInsecureClusterWarning, + displayAlert, + tenant = '/server-base-path', + storageValue, +}: TestParams) { + const coreSetup = coreMock.createSetup(); + (coreSetup.http.basePath.serverBasePath as string) = tenant; + + const coreStart = coreMock.createStart(); + coreStart.http.get.mockResolvedValue({ displayAlert }); + coreStart.notifications.toasts.addWarning.mockReturnValue({ id: 'mock_alert_id' }); + + const config = { showInsecureClusterWarning } as ConfigType; + const storage = coreMock.createStorage(); + if (storageValue) { + storage.getItem.mockReturnValue(storageValue); + } + + const service = new SecurityCheckupService(config, storage); + service.setup(coreSetup); + service.start(coreStart); + await nextTick(); + + return { coreSetup, coreStart, storage }; +} + describe('SecurityCheckupService', () => { - describe('#setup', () => { - it('configures the alert title and text for the default distribution', async () => { - const securityOssSetup = mockSecurityOssPlugin.createSetup(); - const service = new SecurityCheckupService(); - service.setup({ securityOssSetup }); - - expect(securityOssSetup.insecureCluster.setAlertTitle).toHaveBeenCalledWith( - insecureClusterAlertTitle - ); + describe('display scenarios', () => { + it('does not display an alert when the warning is explicitly disabled via config', async () => { + const testParams = { + showInsecureClusterWarning: false, + displayAlert: true, + }; + const { coreStart, storage } = await setupAndStart(testParams); - expect(securityOssSetup.insecureCluster.setAlertText).toHaveBeenCalledTimes(1); + expect(coreStart.http.get).not.toHaveBeenCalled(); + expect(coreStart.notifications.toasts.addWarning).not.toHaveBeenCalled(); + expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); + expect(storage.setItem).not.toHaveBeenCalled(); }); - }); - describe('#start', () => { - it('onDismiss triggers hiding of the alert', async () => { - const securityOssSetup = mockSecurityOssPlugin.createSetup(); - const securityOssStart = mockSecurityOssPlugin.createStart(); - const service = new SecurityCheckupService(); - service.setup({ securityOssSetup }); - service.start({ securityOssStart, docLinks: docLinksServiceMock.createStartContract() }); - expect(securityOssStart.insecureCluster.hideAlert).toHaveBeenCalledTimes(0); + it('does not display an alert when state indicates that alert should not be shown', async () => { + const testParams = { + showInsecureClusterWarning: true, + displayAlert: false, + }; + const { coreStart, storage } = await setupAndStart(testParams); - mockOnDismiss(); + expect(coreStart.http.get).toHaveBeenCalledTimes(1); + expect(coreStart.notifications.toasts.addWarning).not.toHaveBeenCalled(); + expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); + expect(storage.setItem).not.toHaveBeenCalled(); + }); - expect(securityOssStart.insecureCluster.hideAlert).toHaveBeenCalledTimes(1); + it('only reads storage information from the current tenant', async () => { + const testParams = { + showInsecureClusterWarning: true, + displayAlert: false, + tenant: '/my-specific-tenant', + storageValue: JSON.stringify({ show: false }), + }; + const { storage } = await setupAndStart(testParams); + + expect(storage.getItem).toHaveBeenCalledTimes(1); + expect(storage.getItem).toHaveBeenCalledWith( + 'insecureClusterWarningVisibility/my-specific-tenant' + ); + }); + + it('does not display an alert when hidden via storage', async () => { + const testParams = { + showInsecureClusterWarning: true, + displayAlert: true, + storageValue: JSON.stringify({ show: false }), + }; + const { coreStart, storage } = await setupAndStart(testParams); + + expect(coreStart.http.get).not.toHaveBeenCalled(); + expect(coreStart.notifications.toasts.addWarning).not.toHaveBeenCalled(); + expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); + expect(storage.setItem).not.toHaveBeenCalled(); }); - it('configures the doc link correctly', async () => { - const securityOssSetup = mockSecurityOssPlugin.createSetup(); - const securityOssStart = mockSecurityOssPlugin.createStart(); - const service = new SecurityCheckupService(); - service.setup({ securityOssSetup }); - service.start({ securityOssStart, docLinks: docLinksServiceMock.createStartContract() }); + it('displays an alert when persisted preference is corrupted', async () => { + const testParams = { + showInsecureClusterWarning: true, + displayAlert: true, + storageValue: '{ this is a string of invalid JSON', + }; + const { coreStart, storage } = await setupAndStart(testParams); - const [alertText] = securityOssSetup.insecureCluster.setAlertText.mock.calls[0]; + expect(coreStart.http.get).toHaveBeenCalledTimes(1); + expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); + expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); + expect(storage.setItem).not.toHaveBeenCalled(); + }); + + it('displays an alert when enabled via config and endpoint checks', async () => { + const testParams = { + showInsecureClusterWarning: true, + displayAlert: true, + }; + const { coreStart, storage } = await setupAndStart(testParams); + + expect(coreStart.http.get).toHaveBeenCalledTimes(1); + expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); + expect(coreStart.notifications.toasts.addWarning.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "iconType": "alert", + "text": "mock insecure cluster text", + "title": "mock insecure cluster title", + }, + Object { + "toastLifeTimeMs": 864000000, + }, + ] + `); + + expect(coreStart.notifications.toasts.remove).not.toHaveBeenCalled(); + expect(storage.setItem).not.toHaveBeenCalled(); + }); - const container = document.createElement('div'); - (alertText as MountPoint)(container); + it('dismisses the alert when requested, and remembers this preference', async () => { + const testParams = { + showInsecureClusterWarning: true, + displayAlert: true, + }; + const { coreStart, storage } = await setupAndStart(testParams); - const docLink = container - .querySelector('[data-test-subj="learnMoreButton"]') - ?.getAttribute('href'); + expect(coreStart.http.get).toHaveBeenCalledTimes(1); + expect(coreStart.notifications.toasts.addWarning).toHaveBeenCalledTimes(1); - expect(docLink).toMatchInlineSnapshot( - `"https://www.elastic.co/guide/en/elasticsearch/reference/mocked-test-branch/configuring-stack-security.html?blade=kibanasecuritymessage"` + mockOnDismissCallback(true); + expect(coreStart.notifications.toasts.remove).toHaveBeenCalledTimes(1); + expect(storage.setItem).toHaveBeenCalledWith( + 'insecureClusterWarningVisibility/server-base-path', + JSON.stringify({ show: false }) ); }); }); diff --git a/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx b/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx index 02adb85c21a80..4d2e92d2a5079 100644 --- a/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx +++ b/x-pack/plugins/security/public/security_checkup/security_checkup_service.tsx @@ -5,48 +5,128 @@ * 2.0. */ -import type { DocLinksStart } from 'src/core/public'; +import { BehaviorSubject, combineLatest, from } from 'rxjs'; +import { distinctUntilChanged, map } from 'rxjs/operators'; + import type { - SecurityOssPluginSetup, - SecurityOssPluginStart, -} from 'src/plugins/security_oss/public'; + DocLinksStart, + HttpSetup, + HttpStart, + NotificationsStart, + Toast, +} from 'src/core/public'; +import type { SecurityCheckupState } from '../../common/types'; +import type { ConfigType } from '../config'; import { insecureClusterAlertText, insecureClusterAlertTitle } from './components'; interface SetupDeps { - securityOssSetup: SecurityOssPluginSetup; + http: HttpSetup; } interface StartDeps { - securityOssStart: SecurityOssPluginStart; + http: HttpStart; + notifications: NotificationsStart; docLinks: DocLinksStart; } +const DEFAULT_SECURITY_CHECKUP_STATE = Object.freeze({ + displayAlert: false, +}); + export class SecurityCheckupService { - private securityOssStart?: SecurityOssPluginStart; + private enabled: boolean; + + private alertVisibility$: BehaviorSubject; + + private storage: Storage; + + private alertToast?: Toast; - private docLinks?: DocLinksStart; + private storageKey?: string; - public setup({ securityOssSetup }: SetupDeps) { - securityOssSetup.insecureCluster.setAlertTitle(insecureClusterAlertTitle); - securityOssSetup.insecureCluster.setAlertText( - insecureClusterAlertText( - () => this.docLinks!, - (persist: boolean) => this.onDismiss(persist) + constructor(config: Pick, storage: Storage) { + this.storage = storage; + this.enabled = config.showInsecureClusterWarning; + this.alertVisibility$ = new BehaviorSubject(this.enabled); + } + + public setup({ http }: SetupDeps) { + const tenant = http.basePath.serverBasePath; + this.storageKey = `insecureClusterWarningVisibility${tenant}`; + this.enabled = this.enabled && this.getPersistedVisibilityPreference(); + this.alertVisibility$.next(this.enabled); + } + + public start(startDeps: StartDeps) { + if (this.enabled) { + this.initializeAlert(startDeps); + } + } + + private initializeAlert({ http, notifications, docLinks }: StartDeps) { + const appState$ = from(this.getSecurityCheckupState(http)); + + // 10 days is reasonably long enough to call "forever" for a page load. + // Can't go too much longer than this. See https://github.com/elastic/kibana/issues/64264#issuecomment-618400354 + const oneMinute = 60000; + const tenDays = oneMinute * 60 * 24 * 10; + + combineLatest([appState$, this.alertVisibility$]) + .pipe( + map(([{ displayAlert }, isAlertVisible]) => displayAlert && isAlertVisible), + distinctUntilChanged() ) - ); + .subscribe((showAlert) => { + if (showAlert && !this.alertToast) { + this.alertToast = notifications.toasts.addWarning( + { + title: insecureClusterAlertTitle, + text: insecureClusterAlertText(docLinks, (persist: boolean) => + this.setAlertVisibility(false, persist) + ), + iconType: 'alert', + }, + { + toastLifeTimeMs: tenDays, + } + ); + } else if (!showAlert && this.alertToast) { + notifications.toasts.remove(this.alertToast); + this.alertToast = undefined; + } + }); } - public start({ securityOssStart, docLinks }: StartDeps) { - this.securityOssStart = securityOssStart; - this.docLinks = docLinks; + private getSecurityCheckupState(http: HttpStart) { + return http.anonymousPaths.isAnonymous(window.location.pathname) + ? Promise.resolve(DEFAULT_SECURITY_CHECKUP_STATE) + : http + .get('/internal/security/security_checkup/state') + .catch(() => DEFAULT_SECURITY_CHECKUP_STATE); } - private onDismiss(persist: boolean) { - if (this.securityOssStart) { - this.securityOssStart.insecureCluster.hideAlert(persist); + private setAlertVisibility(show: boolean, persist: boolean) { + if (!this.enabled) { + return; + } + this.alertVisibility$.next(show); + if (persist) { + this.setPersistedVisibilityPreference(show); } } - public stop() {} + private getPersistedVisibilityPreference() { + const entry = this.storage.getItem(this.storageKey!) ?? '{}'; + try { + const { show = true } = JSON.parse(entry); + return show; + } catch (e) { + return true; + } + } + + private setPersistedVisibilityPreference(show: boolean) { + this.storage.setItem(this.storageKey!, JSON.stringify({ show })); + } } diff --git a/x-pack/plugins/security/public/session/session_timeout.mock.ts b/x-pack/plugins/security/public/session/session_timeout.mock.ts deleted file mode 100644 index 15071b08ded8f..0000000000000 --- a/x-pack/plugins/security/public/session/session_timeout.mock.ts +++ /dev/null @@ -1,17 +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 - * 2.0; you may not use this file except in compliance with the Elastic License - * 2.0. - */ - -import type { PublicMethodsOf } from '@kbn/utility-types'; - -import type { SessionTimeout } from './session_timeout'; - -export function createSessionTimeoutMock() { - return { - start: jest.fn(), - stop: jest.fn(), - } as jest.Mocked>; -} diff --git a/x-pack/plugins/security/server/config.test.ts b/x-pack/plugins/security/server/config.test.ts index 98f11d56853b2..4a7d8c7961cf5 100644 --- a/x-pack/plugins/security/server/config.test.ts +++ b/x-pack/plugins/security/server/config.test.ts @@ -65,6 +65,7 @@ describe('config schema', () => { "idleTimeout": "PT1H", "lifespan": "P30D", }, + "showInsecureClusterWarning": true, } `); @@ -117,6 +118,7 @@ describe('config schema', () => { "idleTimeout": "PT1H", "lifespan": "P30D", }, + "showInsecureClusterWarning": true, } `); @@ -168,6 +170,7 @@ describe('config schema', () => { "idleTimeout": "PT1H", "lifespan": "P30D", }, + "showInsecureClusterWarning": true, } `); }); diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index af5703cff158c..07ff81e092f5f 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -200,6 +200,7 @@ const providersConfigSchema = schema.object( export const ConfigSchema = schema.object({ enabled: schema.boolean({ defaultValue: true }), loginAssistanceMessage: schema.string({ defaultValue: '' }), + showInsecureClusterWarning: schema.boolean({ defaultValue: true }), loginHelp: schema.maybe(schema.string()), cookieName: schema.string({ defaultValue: 'sid' }), encryptionKey: schema.conditional( diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts index beac88293026c..d9db18cdb96af 100644 --- a/x-pack/plugins/security/server/config_deprecations.test.ts +++ b/x-pack/plugins/security/server/config_deprecations.test.ts @@ -171,6 +171,22 @@ describe('Config Deprecations', () => { `); }); + it('renames security.showInsecureClusterWarning to xpack.security.showInsecureClusterWarning', () => { + const config = { + security: { + showInsecureClusterWarning: false, + }, + }; + const { messages, migrated } = applyConfigDeprecations(cloneDeep(config)); + expect(migrated.security.showInsecureClusterWarning).not.toBeDefined(); + expect(migrated.xpack.security.showInsecureClusterWarning).toEqual(false); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting \\"security.showInsecureClusterWarning\\" has been replaced by \\"xpack.security.showInsecureClusterWarning\\"", + ] + `); + }); + it('warns when using the legacy audit logger', () => { const config = { xpack: { diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts index 169211184a325..681e7699721b5 100644 --- a/x-pack/plugins/security/server/config_deprecations.ts +++ b/x-pack/plugins/security/server/config_deprecations.ts @@ -10,6 +10,7 @@ import type { ConfigDeprecationProvider } from 'src/core/server'; export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ rename, + renameFromRoot, unused, }) => [ rename('sessionTimeout', 'session.idleTimeout'), @@ -21,6 +22,11 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({ rename('audit.appender.strategy.kind', 'audit.appender.strategy.type'), rename('audit.appender.path', 'audit.appender.fileName'), + renameFromRoot( + 'security.showInsecureClusterWarning', + 'xpack.security.showInsecureClusterWarning' + ), + unused('authorization.legacyFallback.enabled'), unused('authc.saml.maxRedirectURLSize'), // Deprecation warning for the legacy audit logger. diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index a48c6833096cc..b3bc85676b07a 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -40,6 +40,7 @@ export const config: PluginConfigDescriptor> = { deprecations: securityConfigDeprecationProvider, exposeToBrowser: { loginAssistanceMessage: true, + showInsecureClusterWarning: true, }, }; export const plugin: PluginInitializer< diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 2ad75a1c53174..98e77038f168a 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -18,7 +18,6 @@ import type { Plugin, PluginInitializerContext, } from 'src/core/server'; -import type { SecurityOssPluginSetup } from 'src/plugins/security_oss/server'; import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import type { @@ -111,7 +110,6 @@ export interface PluginSetupDependencies { licensing: LicensingPluginSetup; taskManager: TaskManagerSetupContract; usageCollection?: UsageCollectionSetup; - securityOss?: SecurityOssPluginSetup; spaces?: SpacesPluginSetup; } @@ -131,7 +129,6 @@ export class SecurityPlugin private readonly logger: Logger; private authorizationSetup?: AuthorizationServiceSetupInternal; private auditSetup?: AuditServiceSetup; - private anonymousAccessStart?: AnonymousAccessServiceStart; private configSubscription?: Subscription; private config?: ConfigType; @@ -191,6 +188,13 @@ export class SecurityPlugin this.initializerContext.logger.get('anonymous-access'), this.getConfig ); + private anonymousAccessStart?: AnonymousAccessServiceStart; + private readonly getAnonymousAccess = () => { + if (!this.anonymousAccessStart) { + throw new Error(`anonymousAccessStart is not registered!`); + } + return this.anonymousAccessStart; + }; constructor(private readonly initializerContext: PluginInitializerContext) { this.logger = this.initializerContext.logger.get(); @@ -198,23 +202,17 @@ export class SecurityPlugin public setup( core: CoreSetup, - { - features, - licensing, - taskManager, - usageCollection, - securityOss, - spaces, - }: PluginSetupDependencies + { features, licensing, taskManager, usageCollection, spaces }: PluginSetupDependencies ) { + const config$ = this.initializerContext.config.create>().pipe( + map((rawConfig) => + createConfig(rawConfig, this.initializerContext.logger.get('config'), { + isTLSEnabled: core.http.getServerInfo().protocol === 'https', + }) + ) + ); this.configSubscription = combineLatest([ - this.initializerContext.config.create>().pipe( - map((rawConfig) => - createConfig(rawConfig, this.initializerContext.logger.get('config'), { - isTLSEnabled: core.http.getServerInfo().protocol === 'https', - }) - ) - ), + config$, this.initializerContext.config.legacy.globalConfig$, ]).subscribe(([config, { kibana }]) => { this.config = config; @@ -234,20 +232,6 @@ export class SecurityPlugin license$: licensing.license$, }); - if (securityOss) { - license.features$.subscribe(({ allowRbac }) => { - const showInsecureClusterWarning = !allowRbac; - securityOss.showInsecureClusterWarning$.next(showInsecureClusterWarning); - }); - - securityOss.setAnonymousAccessServiceProvider(() => { - if (!this.anonymousAccessStart) { - throw new Error('AnonymousAccess service is not started!'); - } - return this.anonymousAccessStart; - }); - } - securityFeatures.forEach((securityFeature) => features.registerElasticsearchFeature(securityFeature) ); @@ -312,6 +296,7 @@ export class SecurityPlugin httpResources: core.http.resources, logger: this.initializerContext.logger.get('routes'), config, + config$, authz: this.authorizationSetup, license, getSession: this.getSession, @@ -319,6 +304,7 @@ export class SecurityPlugin startServicesPromise.then((services) => services.features.getKibanaFeatures()), getFeatureUsageService: this.getFeatureUsageService, getAuthenticationService: this.getAuthentication, + getAnonymousAccessService: this.getAnonymousAccess, }); return Object.freeze({ diff --git a/x-pack/plugins/security/server/routes/anonymous_access/get_capabilities.test.ts b/x-pack/plugins/security/server/routes/anonymous_access/get_capabilities.test.ts new file mode 100644 index 0000000000000..d840351ea7dc6 --- /dev/null +++ b/x-pack/plugins/security/server/routes/anonymous_access/get_capabilities.test.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import { routeDefinitionParamsMock, securityRequestHandlerContextMock } from '../index.mock'; +import { defineAnonymousAccessGetCapabilitiesRoutes } from './get_capabilities'; + +describe('GET /internal/security/anonymous_access/capabilities', () => { + it('returns anonymous access state', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.getAnonymousAccessService.mockReturnValue({ + isAnonymousAccessEnabled: true, + accessURLParameters: new Map([['auth_provider_hint', 'anonymous1']]), + getCapabilities: jest.fn().mockResolvedValue({ + navLinks: {}, + management: {}, + catalogue: {}, + custom: { something: true }, + }), + }); + const mockContext = securityRequestHandlerContextMock.create(); + + defineAnonymousAccessGetCapabilitiesRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/anonymous_access/capabilities`, + headers, + }); + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ + navLinks: {}, + management: {}, + catalogue: {}, + custom: { something: true }, + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/anonymous_access/get_capabilities.ts b/x-pack/plugins/security/server/routes/anonymous_access/get_capabilities.ts new file mode 100644 index 0000000000000..2a2f495cf062f --- /dev/null +++ b/x-pack/plugins/security/server/routes/anonymous_access/get_capabilities.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RouteDefinitionParams } from '../'; + +/** + * Defines route that returns capabilities of the anonymous service account. + */ +export function defineAnonymousAccessGetCapabilitiesRoutes({ + router, + getAnonymousAccessService, +}: RouteDefinitionParams) { + router.get( + { path: '/internal/security/anonymous_access/capabilities', validate: false }, + async (_context, request, response) => { + const anonymousAccessService = getAnonymousAccessService(); + return response.ok({ body: await anonymousAccessService.getCapabilities(request) }); + } + ); +} diff --git a/x-pack/plugins/security/server/routes/anonymous_access/get_state.test.ts b/x-pack/plugins/security/server/routes/anonymous_access/get_state.test.ts new file mode 100644 index 0000000000000..80dbf5cada9ee --- /dev/null +++ b/x-pack/plugins/security/server/routes/anonymous_access/get_state.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import type { AnonymousAccessServiceStart } from '../../anonymous_access'; +import { routeDefinitionParamsMock, securityRequestHandlerContextMock } from '../index.mock'; +import { defineAnonymousAccessGetStateRoutes } from './get_state'; + +describe('GET /internal/security/anonymous_access/state', () => { + function doMockAndTest(accessURLParameters: AnonymousAccessServiceStart['accessURLParameters']) { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + mockRouteDefinitionParams.getAnonymousAccessService.mockReturnValue({ + isAnonymousAccessEnabled: true, + accessURLParameters, + getCapabilities: jest.fn(), + }); + const mockContext = securityRequestHandlerContextMock.create(); + + defineAnonymousAccessGetStateRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/anonymous_access/state`, + headers, + }); + return handler(mockContext, mockRequest, kibanaResponseFactory); + } + + it('returns anonymous access state (with access URL parameters)', async () => { + const response = await doMockAndTest(new Map([['auth_provider_hint', 'anonymous1']])); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ + isEnabled: true, + accessURLParameters: { auth_provider_hint: 'anonymous1' }, + }); + }); + + it('returns anonymous access state (without access URL parameters)', async () => { + const response = await doMockAndTest(null); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ + isEnabled: true, + accessURLParameters: null, + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/anonymous_access/get_state.ts b/x-pack/plugins/security/server/routes/anonymous_access/get_state.ts new file mode 100644 index 0000000000000..f817ade1a10fd --- /dev/null +++ b/x-pack/plugins/security/server/routes/anonymous_access/get_state.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RouteDefinitionParams } from '..'; +import type { AnonymousAccessState } from '../../../../../../src/plugins/share/common'; + +/** + * Defines route that returns the state of anonymous access -- whether anonymous access is enabled, and what additional parameters should be + * added to the URL (if any). + */ +export function defineAnonymousAccessGetStateRoutes({ + router, + getAnonymousAccessService, +}: RouteDefinitionParams) { + router.get( + { path: '/internal/security/anonymous_access/state', validate: false }, + async (_context, _request, response) => { + const anonymousAccessService = getAnonymousAccessService(); + const accessURLParameters = anonymousAccessService.accessURLParameters + ? Object.fromEntries(anonymousAccessService.accessURLParameters.entries()) + : null; + const responseBody: AnonymousAccessState = { + isEnabled: anonymousAccessService.isAnonymousAccessEnabled, + accessURLParameters, + }; + return response.ok({ body: responseBody }); + } + ); +} diff --git a/x-pack/plugins/security/server/routes/anonymous_access/index.ts b/x-pack/plugins/security/server/routes/anonymous_access/index.ts new file mode 100644 index 0000000000000..53ed33e8741db --- /dev/null +++ b/x-pack/plugins/security/server/routes/anonymous_access/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { RouteDefinitionParams } from '../'; +import { defineAnonymousAccessGetCapabilitiesRoutes } from './get_capabilities'; +import { defineAnonymousAccessGetStateRoutes } from './get_state'; + +export function defineAnonymousAccessRoutes(params: RouteDefinitionParams) { + defineAnonymousAccessGetCapabilitiesRoutes(params); + defineAnonymousAccessGetStateRoutes(params); +} diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index a92884c1dab75..9b6e7948838c8 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -5,26 +5,39 @@ * 2.0. */ +import { BehaviorSubject } from 'rxjs'; + import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; -import { httpResourcesMock, httpServiceMock, loggingSystemMock } from 'src/core/server/mocks'; +import { + coreMock, + httpResourcesMock, + httpServiceMock, + loggingSystemMock, +} from 'src/core/server/mocks'; +import { licensingMock } from '../../../licensing/server/mocks'; import { licenseMock } from '../../common/licensing/index.mock'; import { authenticationServiceMock } from '../authentication/authentication_service.mock'; import { authorizationMock } from '../authorization/index.mock'; import { ConfigSchema, createConfig } from '../config'; import { sessionMock } from '../session_management/session.mock'; +import type { SecurityRequestHandlerContext } from '../types'; import type { RouteDefinitionParams } from './'; export const routeDefinitionParamsMock = { - create: (config: Record = {}) => - ({ + create: (rawConfig: Record = {}) => { + const config = createConfig( + ConfigSchema.validate(rawConfig), + loggingSystemMock.create().get(), + { isTLSEnabled: false } + ); + return { router: httpServiceMock.createRouter(), basePath: httpServiceMock.createBasePath(), csp: httpServiceMock.createSetupContract().csp, logger: loggingSystemMock.create().get(), - config: createConfig(ConfigSchema.validate(config), loggingSystemMock.create().get(), { - isTLSEnabled: false, - }), + config, + config$: new BehaviorSubject(config).asObservable(), authz: authorizationMock.create(), license: licenseMock.create(), httpResources: httpResourcesMock.createRegistrar(), @@ -32,5 +45,14 @@ export const routeDefinitionParamsMock = { getFeatureUsageService: jest.fn(), getSession: jest.fn().mockReturnValue(sessionMock.create()), getAuthenticationService: jest.fn().mockReturnValue(authenticationServiceMock.createStart()), - } as unknown as DeeplyMockedKeys), + getAnonymousAccessService: jest.fn(), + } as unknown as DeeplyMockedKeys; + }, +}; + +export const securityRequestHandlerContextMock = { + create: (): SecurityRequestHandlerContext => ({ + core: coreMock.createRequestHandlerContext(), + licensing: licensingMock.createRequestHandlerContext(), + }), }; diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 7a4310da3e4c7..851e70a357cf9 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -5,22 +5,27 @@ * 2.0. */ +import type { Observable } from 'rxjs'; + import type { PublicMethodsOf } from '@kbn/utility-types'; import type { HttpResources, IBasePath, Logger } from 'src/core/server'; import type { KibanaFeature } from '../../../features/server'; import type { SecurityLicense } from '../../common/licensing'; +import type { AnonymousAccessServiceStart } from '../anonymous_access'; import type { InternalAuthenticationServiceStart } from '../authentication'; import type { AuthorizationServiceSetupInternal } from '../authorization'; import type { ConfigType } from '../config'; import type { SecurityFeatureUsageServiceStart } from '../feature_usage'; import type { Session } from '../session_management'; import type { SecurityRouter } from '../types'; +import { defineAnonymousAccessRoutes } from './anonymous_access'; import { defineApiKeysRoutes } from './api_keys'; import { defineAuthenticationRoutes } from './authentication'; import { defineAuthorizationRoutes } from './authorization'; import { defineIndicesRoutes } from './indices'; import { defineRoleMappingRoutes } from './role_mapping'; +import { defineSecurityCheckupGetStateRoutes } from './security_checkup'; import { defineSessionManagementRoutes } from './session_management'; import { defineUsersRoutes } from './users'; import { defineViewRoutes } from './views'; @@ -34,12 +39,14 @@ export interface RouteDefinitionParams { httpResources: HttpResources; logger: Logger; config: ConfigType; + config$: Observable; authz: AuthorizationServiceSetupInternal; getSession: () => PublicMethodsOf; license: SecurityLicense; getFeatures: () => Promise; getFeatureUsageService: () => SecurityFeatureUsageServiceStart; getAuthenticationService: () => InternalAuthenticationServiceStart; + getAnonymousAccessService: () => AnonymousAccessServiceStart; } export function defineRoutes(params: RouteDefinitionParams) { @@ -51,4 +58,6 @@ export function defineRoutes(params: RouteDefinitionParams) { defineUsersRoutes(params); defineRoleMappingRoutes(params); defineViewRoutes(params); + defineAnonymousAccessRoutes(params); + defineSecurityCheckupGetStateRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/security_checkup/get_state.test.mock.ts b/x-pack/plugins/security/server/routes/security_checkup/get_state.test.mock.ts new file mode 100644 index 0000000000000..57e1a20be966a --- /dev/null +++ b/x-pack/plugins/security/server/routes/security_checkup/get_state.test.mock.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import type { createClusterDataCheck } from '../../security_checkup'; + +export const mockCreateClusterDataCheck = jest.fn() as jest.MockedFunction< + typeof createClusterDataCheck +>; + +jest.mock('../../security_checkup', () => ({ + createClusterDataCheck: mockCreateClusterDataCheck, +})); diff --git a/x-pack/plugins/security/server/routes/security_checkup/get_state.test.ts b/x-pack/plugins/security/server/routes/security_checkup/get_state.test.ts new file mode 100644 index 0000000000000..9986c1e979b8b --- /dev/null +++ b/x-pack/plugins/security/server/routes/security_checkup/get_state.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +// eslint-disable-next-line import/order +import { mockCreateClusterDataCheck } from './get_state.test.mock'; + +import type { Observable } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; + +import { kibanaResponseFactory } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +import type { SecurityLicenseFeatures } from '../../../common/licensing'; +import { licenseMock } from '../../../common/licensing/index.mock'; +import { routeDefinitionParamsMock, securityRequestHandlerContextMock } from '../index.mock'; +import { defineSecurityCheckupGetStateRoutes } from './get_state'; + +interface SetupParams { + showInsecureClusterWarning: boolean; + allowRbac: boolean; + doesClusterHaveUserData: boolean; +} + +function setup({ showInsecureClusterWarning, allowRbac, doesClusterHaveUserData }: SetupParams) { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + const configSubject = new BehaviorSubject({ showInsecureClusterWarning }); + (mockRouteDefinitionParams.config$ as Observable<{ showInsecureClusterWarning: boolean }>) = + configSubject.asObservable(); + + const licenseWithFeatures = licenseMock.create(); + const featuresSubject = new BehaviorSubject({ allowRbac } as SecurityLicenseFeatures); + licenseWithFeatures.features$ = featuresSubject.asObservable(); + + const mockClusterDataCheck = jest.fn().mockResolvedValue(doesClusterHaveUserData); + mockCreateClusterDataCheck.mockReturnValue(mockClusterDataCheck); + + const mockContext = securityRequestHandlerContextMock.create(); + defineSecurityCheckupGetStateRoutes({ + ...mockRouteDefinitionParams, + license: licenseWithFeatures, + }); + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/anonymous_access/state`, + headers, + }); + + return { + configSubject, + featuresSubject, + mockClusterDataCheck, + simulateRequest: () => handler(mockContext, mockRequest, kibanaResponseFactory), + }; +} + +describe('GET /internal/security/security_checkup/state', () => { + it('responds `displayAlert == false` if plugin is not configured to display alerts', async () => { + const { simulateRequest, mockClusterDataCheck } = setup({ + showInsecureClusterWarning: false, + allowRbac: false, + doesClusterHaveUserData: true, + }); + + const response = await simulateRequest(); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ displayAlert: false }); + expect(mockClusterDataCheck).not.toHaveBeenCalled(); + }); + + it('responds `displayAlert == false` if Elasticsearch security is already enabled', async () => { + const { simulateRequest, mockClusterDataCheck } = setup({ + showInsecureClusterWarning: true, + allowRbac: true, + doesClusterHaveUserData: true, + }); + + const response = await simulateRequest(); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ displayAlert: false }); + expect(mockClusterDataCheck).not.toHaveBeenCalled(); + }); + + it('responds `displayAlert == false` if the cluster does not contain user data', async () => { + const { simulateRequest, mockClusterDataCheck } = setup({ + showInsecureClusterWarning: true, + allowRbac: false, + doesClusterHaveUserData: false, + }); + + const response = await simulateRequest(); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ displayAlert: false }); + // since the plugin is configured to display alerts AND Elasticsearch security is disabled, we checked the cluster to see if it contained user data + expect(mockClusterDataCheck).toHaveBeenCalledTimes(1); + }); + + it('responds `displayAlert == true` if all conditions are met', async () => { + const { simulateRequest, mockClusterDataCheck } = setup({ + showInsecureClusterWarning: true, + allowRbac: false, + doesClusterHaveUserData: true, + }); + + const response = await simulateRequest(); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ displayAlert: true }); + expect(mockClusterDataCheck).toHaveBeenCalledTimes(1); + }); + + it('handles state changes', async () => { + const { configSubject, featuresSubject, simulateRequest, mockClusterDataCheck } = setup({ + showInsecureClusterWarning: false, + allowRbac: false, + doesClusterHaveUserData: true, + }); + + const response1 = await simulateRequest(); + expect(response1.status).toBe(200); + expect(response1.payload).toEqual({ displayAlert: false }); + expect(mockClusterDataCheck).not.toHaveBeenCalled(); + + configSubject.next({ showInsecureClusterWarning: true }); // enable insecure cluster warning + const response2 = await simulateRequest(); + expect(response2.status).toBe(200); + expect(response2.payload).toEqual({ displayAlert: true }); // now that the warning is enabled, all conditions are met and it should be displayed + expect(mockClusterDataCheck).toHaveBeenCalledTimes(1); + + featuresSubject.next({ allowRbac: true } as SecurityLicenseFeatures); // enable Elasticsearch security + const response3 = await simulateRequest(); + expect(response3.status).toBe(200); + expect(response3.payload).toEqual({ displayAlert: false }); // now that Elasticsearch security is enabled, we don't need to display the alert anymore + expect(mockClusterDataCheck).toHaveBeenCalledTimes(1); // we did not check the cluster for data again because Elasticsearch security is enabled + }); +}); diff --git a/x-pack/plugins/security/server/routes/security_checkup/get_state.ts b/x-pack/plugins/security/server/routes/security_checkup/get_state.ts new file mode 100644 index 0000000000000..8c4e69cb87c81 --- /dev/null +++ b/x-pack/plugins/security/server/routes/security_checkup/get_state.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +import { combineLatest } from 'rxjs'; + +import type { RouteDefinitionParams } from '..'; +import type { SecurityCheckupState } from '../../../common/types'; +import { createClusterDataCheck } from '../../security_checkup'; + +/** + * Defines route that returns the state of the security checkup feature. + */ +export function defineSecurityCheckupGetStateRoutes({ + router, + logger, + config$, + license, +}: RouteDefinitionParams) { + let showInsecureClusterWarning = false; + + combineLatest([config$, license.features$]).subscribe(([config, { allowRbac }]) => { + showInsecureClusterWarning = config.showInsecureClusterWarning && !allowRbac; + }); + + const doesClusterHaveUserData = createClusterDataCheck(); + + router.get( + { path: '/internal/security/security_checkup/state', validate: false }, + async (context, _request, response) => { + let displayAlert = false; + if (showInsecureClusterWarning) { + displayAlert = await doesClusterHaveUserData( + context.core.elasticsearch.client.asInternalUser, + logger + ); + } + + const state: SecurityCheckupState = { + displayAlert, + }; + return response.ok({ body: state }); + } + ); +} diff --git a/x-pack/plugins/security/server/routes/security_checkup/index.ts b/x-pack/plugins/security/server/routes/security_checkup/index.ts new file mode 100644 index 0000000000000..ff479dd16c671 --- /dev/null +++ b/x-pack/plugins/security/server/routes/security_checkup/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { defineSecurityCheckupGetStateRoutes } from './get_state'; diff --git a/src/plugins/security_oss/server/check_cluster_data.test.ts b/x-pack/plugins/security/server/security_checkup/check_cluster_data.test.ts similarity index 95% rename from src/plugins/security_oss/server/check_cluster_data.test.ts rename to x-pack/plugins/security/server/security_checkup/check_cluster_data.test.ts index 6aa1cc9a28c39..396e06bd1d04e 100644 --- a/src/plugins/security_oss/server/check_cluster_data.test.ts +++ b/x-pack/plugins/security/server/security_checkup/check_cluster_data.test.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; diff --git a/src/plugins/security_oss/server/check_cluster_data.ts b/x-pack/plugins/security/server/security_checkup/check_cluster_data.ts similarity index 84% rename from src/plugins/security_oss/server/check_cluster_data.ts rename to x-pack/plugins/security/server/security_checkup/check_cluster_data.ts index 19a4145333dd0..bec8adfdc4f6b 100644 --- a/src/plugins/security_oss/server/check_cluster_data.ts +++ b/x-pack/plugins/security/server/security_checkup/check_cluster_data.ts @@ -1,9 +1,8 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. */ import type { ElasticsearchClient, Logger } from 'src/core/server'; diff --git a/x-pack/plugins/security/server/security_checkup/index.ts b/x-pack/plugins/security/server/security_checkup/index.ts new file mode 100644 index 0000000000000..fa4b5f67f8511 --- /dev/null +++ b/x-pack/plugins/security/server/security_checkup/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +export { createClusterDataCheck } from './check_cluster_data'; diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index ea03b9dbb6471..5cc25bbb44055 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -17,7 +17,7 @@ { "path": "../../../src/plugins/home/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/management/tsconfig.json" }, - { "path": "../../../src/plugins/security_oss/tsconfig.json" }, + { "path": "../../../src/plugins/share/tsconfig.json" }, { "path": "../../../src/plugins/usage_collection/tsconfig.json" } ] } diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 9a1e41f223ed6..9b2f783c6f0a6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4455,11 +4455,6 @@ "savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "このオブジェクトに関連付けられたインデックスパターンは現在存在しません。", "savedObjectsManagement.view.savedObjectProblemErrorMessage": "この保存されたオブジェクトに問題があります", "savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "このオブジェクトに関連付けられた保存された検索は現在存在しません。", - "security.checkup.dismissButtonText": "閉じる", - "security.checkup.dontShowAgain": "今後表示しない", - "security.checkup.insecureClusterMessage": "1 ビットを失わないでください。Elastic では無料でデータを保護できます。", - "security.checkup.insecureClusterTitle": "データが保護されていません", - "security.checkup.learnMoreButtonText": "詳細", "share.advancedSettings.csv.quoteValuesText": "csvエクスポートに値を引用するかどうかです", "share.advancedSettings.csv.quoteValuesTitle": "CSVの値を引用", "share.advancedSettings.csv.separatorText": "エクスポートされた値をこの文字列で区切ります", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 73223eca1c98c..f81202c66e7e2 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -4498,11 +4498,6 @@ "savedObjectsManagement.view.indexPatternDoesNotExistErrorMessage": "与此对象关联的索引模式已不存在。", "savedObjectsManagement.view.savedObjectProblemErrorMessage": "此已保存对象有问题", "savedObjectsManagement.view.savedSearchDoesNotExistErrorMessage": "与此对象关联的已保存搜索已不存在。", - "security.checkup.dismissButtonText": "关闭", - "security.checkup.dontShowAgain": "不再显示", - "security.checkup.insecureClusterMessage": "不要丢失一位。使用 Elastic,免费保护您的数据。", - "security.checkup.insecureClusterTitle": "您的数据并非安全无忧", - "security.checkup.learnMoreButtonText": "了解详情", "share.advancedSettings.csv.quoteValuesText": "在 CSV 导出中是否应使用引号引起值?", "share.advancedSettings.csv.quoteValuesTitle": "使用引号引起 CSV 值", "share.advancedSettings.csv.separatorText": "使用此字符串分隔导出的值", diff --git a/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts b/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts index 886fa6f3fe7e3..baa9e9ff419a4 100644 --- a/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts +++ b/x-pack/test/security_api_integration/tests/anonymous/capabilities.ts @@ -19,7 +19,7 @@ export default function ({ getService }: FtrProviderContext) { async function getAnonymousCapabilities(spaceId?: string) { const apiResponse = await supertest - .get(`${spaceId ? `/s/${spaceId}` : ''}/internal/security_oss/anonymous_access/capabilities`) + .get(`${spaceId ? `/s/${spaceId}` : ''}/internal/security/anonymous_access/capabilities`) .expect(200); return Object.fromEntries(