mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Add Mock IDP login page and role switcher (#172257)
This commit is contained in:
parent
1865d4dab4
commit
7bee86d6eb
38 changed files with 686 additions and 161 deletions
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
|
@ -553,6 +553,7 @@ x-pack/packages/ml/trained_models_utils @elastic/ml-ui
|
|||
x-pack/packages/ml/ui_actions @elastic/ml-ui
|
||||
x-pack/packages/ml/url_state @elastic/ml-ui
|
||||
packages/kbn-mock-idp-plugin @elastic/kibana-security
|
||||
packages/kbn-mock-idp-utils @elastic/kibana-security
|
||||
packages/kbn-monaco @elastic/appex-sharedux
|
||||
x-pack/plugins/monitoring_collection @elastic/obs-ux-infra_services-team
|
||||
x-pack/plugins/monitoring @elastic/obs-ux-infra_services-team
|
||||
|
|
|
@ -1269,6 +1269,7 @@
|
|||
"@kbn/managed-vscode-config-cli": "link:packages/kbn-managed-vscode-config-cli",
|
||||
"@kbn/management-storybook-config": "link:packages/kbn-management/storybook/config",
|
||||
"@kbn/mock-idp-plugin": "link:packages/kbn-mock-idp-plugin",
|
||||
"@kbn/mock-idp-utils": "link:packages/kbn-mock-idp-utils",
|
||||
"@kbn/openapi-bundler": "link:packages/kbn-openapi-bundler",
|
||||
"@kbn/openapi-generator": "link:packages/kbn-openapi-generator",
|
||||
"@kbn/optimizer": "link:packages/kbn-optimizer",
|
||||
|
|
|
@ -47,7 +47,12 @@ export type HttpResourcesResponseOptions = HttpResponseOptions;
|
|||
export interface HttpResourcesServiceToolkit {
|
||||
/** To respond with HTML page bootstrapping Kibana application. */
|
||||
renderCoreApp: (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse>;
|
||||
/** To respond with HTML page bootstrapping Kibana application without retrieving user-specific information. */
|
||||
/**
|
||||
* To respond with HTML page bootstrapping Kibana application without retrieving user-specific information.
|
||||
* **Note:**
|
||||
* - Your client-side JavaScript bundle will only be loaded on an anonymous page if `plugin.enabledOnAnonymousPages` is enabled in your plugin's `kibana.jsonc` manifest file.
|
||||
* - You will also need to register the route serving your anonymous app with the `coreSetup.http.anonymousPaths` service in your plugin's client-side `setup` method.
|
||||
* */
|
||||
renderAnonymousCoreApp: (options?: HttpResourcesRenderOptions) => Promise<IKibanaResponse>;
|
||||
/** To respond with a custom HTML page. */
|
||||
renderHtml: (options: HttpResourcesResponseOptions) => IKibanaResponse;
|
||||
|
|
|
@ -41,7 +41,7 @@ import {
|
|||
} from '../paths';
|
||||
import * as waitClusterUtil from './wait_until_cluster_ready';
|
||||
import * as waitForSecurityIndexUtil from './wait_for_security_index';
|
||||
import * as mockIdpPluginUtil from '@kbn/mock-idp-plugin/common';
|
||||
import * as mockIdpPluginUtil from '@kbn/mock-idp-utils';
|
||||
|
||||
jest.mock('execa');
|
||||
const execa = jest.requireMock('execa');
|
||||
|
@ -59,7 +59,7 @@ jest.mock('./wait_for_security_index', () => ({
|
|||
waitForSecurityIndex: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@kbn/mock-idp-plugin/common');
|
||||
jest.mock('@kbn/mock-idp-utils');
|
||||
|
||||
const log = new ToolingLog();
|
||||
const logWriter = new ToolingLogCollectingWriter();
|
||||
|
|
|
@ -24,7 +24,7 @@ import {
|
|||
MOCK_IDP_ATTRIBUTE_NAME,
|
||||
ensureSAMLRoleMapping,
|
||||
createMockIdpMetadata,
|
||||
} from '@kbn/mock-idp-plugin/common';
|
||||
} from '@kbn/mock-idp-utils';
|
||||
|
||||
import { waitForSecurityIndex } from './wait_for_security_index';
|
||||
import { createCliError } from '../errors';
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
"@kbn/dev-utils",
|
||||
"@kbn/dev-proc-runner",
|
||||
"@kbn/ci-stats-reporter",
|
||||
"@kbn/mock-idp-plugin",
|
||||
"@kbn/mock-idp-utils",
|
||||
"@kbn/jest-serializers",
|
||||
"@kbn/repo-info"
|
||||
]
|
||||
|
|
|
@ -6,22 +6,4 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export {
|
||||
MOCK_IDP_PLUGIN_PATH,
|
||||
MOCK_IDP_METADATA_PATH,
|
||||
MOCK_IDP_LOGIN_PATH,
|
||||
MOCK_IDP_LOGOUT_PATH,
|
||||
MOCK_IDP_REALM_NAME,
|
||||
MOCK_IDP_ENTITY_ID,
|
||||
MOCK_IDP_ROLE_MAPPING_NAME,
|
||||
MOCK_IDP_ATTRIBUTE_PRINCIPAL,
|
||||
MOCK_IDP_ATTRIBUTE_ROLES,
|
||||
MOCK_IDP_ATTRIBUTE_EMAIL,
|
||||
MOCK_IDP_ATTRIBUTE_NAME,
|
||||
} from './constants';
|
||||
export {
|
||||
createMockIdpMetadata,
|
||||
createSAMLResponse,
|
||||
ensureSAMLRoleMapping,
|
||||
parseSAMLAuthnRequest,
|
||||
} from './utils';
|
||||
export { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils';
|
||||
|
|
|
@ -6,6 +6,13 @@
|
|||
"plugin": {
|
||||
"id": "mockIdpPlugin",
|
||||
"server": true,
|
||||
"browser": false
|
||||
"browser": true,
|
||||
"enabledOnAnonymousPages": true,
|
||||
"requiredPlugins": [
|
||||
"cloud"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"kibanaReact"
|
||||
]
|
||||
}
|
||||
}
|
9
packages/kbn-mock-idp-plugin/public/index.ts
Normal file
9
packages/kbn-mock-idp-plugin/public/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 { plugin } from './plugin';
|
152
packages/kbn-mock-idp-plugin/public/login_page.tsx
Normal file
152
packages/kbn-mock-idp-plugin/public/login_page.tsx
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
* 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,
|
||||
EuiPageTemplate,
|
||||
EuiEmptyPrompt,
|
||||
EuiComboBox,
|
||||
EuiInlineEditTitle,
|
||||
EuiFormRow,
|
||||
EuiSpacer,
|
||||
EuiComboBoxOptionOption,
|
||||
EuiButtonEmpty,
|
||||
} from '@elastic/eui';
|
||||
import React, { ChangeEvent, FunctionComponent } from 'react';
|
||||
import { FormikProvider, useFormik, Field, Form } from 'formik';
|
||||
|
||||
import {
|
||||
MOCK_IDP_SECURITY_ROLE_NAMES,
|
||||
MOCK_IDP_OBSERVABILITY_ROLE_NAMES,
|
||||
MOCK_IDP_SEARCH_ROLE_NAMES,
|
||||
} from '@kbn/mock-idp-utils/src/constants';
|
||||
import { useAuthenticator } from './role_switcher';
|
||||
|
||||
export interface LoginPageProps {
|
||||
projectType?: string;
|
||||
}
|
||||
|
||||
export const LoginPage: FunctionComponent<LoginPageProps> = ({ projectType }) => {
|
||||
const roles =
|
||||
projectType === 'security'
|
||||
? MOCK_IDP_SECURITY_ROLE_NAMES
|
||||
: projectType === 'observability'
|
||||
? MOCK_IDP_OBSERVABILITY_ROLE_NAMES
|
||||
: MOCK_IDP_SEARCH_ROLE_NAMES;
|
||||
|
||||
const [, switchCurrentUser] = useAuthenticator(true);
|
||||
const formik = useFormik({
|
||||
initialValues: {
|
||||
full_name: 'Test User',
|
||||
role: roles[0],
|
||||
},
|
||||
async onSubmit(values) {
|
||||
await switchCurrentUser({
|
||||
username: sanitizeUsername(values.full_name),
|
||||
full_name: values.full_name,
|
||||
email: sanitizeEmail(values.full_name),
|
||||
roles: [values.role],
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<FormikProvider value={formik}>
|
||||
<EuiPageTemplate panelled={false}>
|
||||
<EuiPageTemplate.Section alignment="center">
|
||||
<Form>
|
||||
<EuiEmptyPrompt
|
||||
iconType="user"
|
||||
layout="vertical"
|
||||
color="plain"
|
||||
body={
|
||||
<>
|
||||
<Field
|
||||
as={EuiInlineEditTitle}
|
||||
name="full_name"
|
||||
heading="h2"
|
||||
inputAriaLabel="Edit name inline"
|
||||
value={formik.values.full_name}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
formik.setFieldValue('full_name', event.target.value);
|
||||
}}
|
||||
onCancel={(previousValue: string) => {
|
||||
formik.setFieldValue('full_name', previousValue);
|
||||
}}
|
||||
isReadOnly={formik.isSubmitting}
|
||||
editModeProps={{
|
||||
formRowProps: {
|
||||
error: formik.errors.full_name,
|
||||
},
|
||||
}}
|
||||
validate={(value: string) => {
|
||||
if (value.trim().length === 0) {
|
||||
return 'Name cannot be empty';
|
||||
}
|
||||
}}
|
||||
isInvalid={!!formik.errors.full_name}
|
||||
placeholder="Enter your name"
|
||||
css={{ width: 350 }}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
|
||||
<EuiFormRow error={formik.errors.role} isInvalid={!!formik.errors.role}>
|
||||
<Field
|
||||
as={EuiComboBox}
|
||||
name="role"
|
||||
placeholder="Select your role"
|
||||
singleSelection={{ asPlainText: true }}
|
||||
options={roles.map((role) => ({ label: role }))}
|
||||
selectedOptions={
|
||||
formik.values.role ? [{ label: formik.values.role }] : undefined
|
||||
}
|
||||
onCreateOption={(value: string) => {
|
||||
formik.setFieldValue('role', value);
|
||||
}}
|
||||
onChange={(selectedOptions: EuiComboBoxOptionOption[]) => {
|
||||
formik.setFieldValue(
|
||||
'role',
|
||||
selectedOptions.length === 0 ? '' : selectedOptions[0].label
|
||||
);
|
||||
}}
|
||||
validate={(value: string) => {
|
||||
if (value.trim().length === 0) {
|
||||
return 'Role cannot be empty';
|
||||
}
|
||||
}}
|
||||
isInvalid={!!formik.errors.role}
|
||||
isClearable={false}
|
||||
fullWidth
|
||||
/>
|
||||
</EuiFormRow>
|
||||
</>
|
||||
}
|
||||
actions={[
|
||||
<EuiButton
|
||||
type="submit"
|
||||
disabled={!formik.isValid}
|
||||
isLoading={formik.isSubmitting}
|
||||
fill
|
||||
>
|
||||
Log in
|
||||
</EuiButton>,
|
||||
<EuiButtonEmpty size="xs" href="/">
|
||||
More login options
|
||||
</EuiButtonEmpty>,
|
||||
]}
|
||||
/>
|
||||
</Form>
|
||||
</EuiPageTemplate.Section>
|
||||
</EuiPageTemplate>
|
||||
</FormikProvider>
|
||||
);
|
||||
};
|
||||
|
||||
const sanitizeUsername = (username: string) =>
|
||||
username.replace(/[^a-zA-Z0-9_]/g, '_').toLowerCase();
|
||||
const sanitizeEmail = (email: string) => `${sanitizeUsername(email)}@elastic.co`;
|
84
packages/kbn-mock-idp-plugin/public/plugin.tsx
Normal file
84
packages/kbn-mock-idp-plugin/public/plugin.tsx
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* 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 { PluginInitializer } from '@kbn/core-plugins-browser';
|
||||
import { AppNavLinkStatus } from '@kbn/core-application-browser';
|
||||
import React from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import { KibanaThemeProvider } from '@kbn/react-kibana-context-theme';
|
||||
import { KibanaContextProvider } from '@kbn/kibana-react-plugin/public';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
import { MOCK_IDP_LOGIN_PATH } from '@kbn/mock-idp-utils/src/constants';
|
||||
import type { CloudStart, CloudSetup } from '@kbn/cloud-plugin/public';
|
||||
import { RoleSwitcher } from './role_switcher';
|
||||
|
||||
export interface PluginSetupDependencies {
|
||||
cloud?: CloudSetup;
|
||||
}
|
||||
|
||||
export interface PluginStartDependencies {
|
||||
cloud?: CloudStart;
|
||||
}
|
||||
|
||||
export const plugin: PluginInitializer<
|
||||
void,
|
||||
void,
|
||||
PluginSetupDependencies,
|
||||
PluginStartDependencies
|
||||
> = () => ({
|
||||
setup(coreSetup, plugins) {
|
||||
// Register Mock IDP login page
|
||||
coreSetup.http.anonymousPaths.register(MOCK_IDP_LOGIN_PATH);
|
||||
coreSetup.application.register({
|
||||
id: 'mock_idp',
|
||||
title: 'Mock IDP',
|
||||
chromeless: true,
|
||||
appRoute: MOCK_IDP_LOGIN_PATH,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
mount: async (params) => {
|
||||
const [[coreStart], { LoginPage }] = await Promise.all([
|
||||
coreSetup.getStartServices(),
|
||||
import('./login_page'),
|
||||
]);
|
||||
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme={coreStart.theme}>
|
||||
<KibanaContextProvider services={coreStart}>
|
||||
<I18nProvider>
|
||||
<LoginPage projectType={plugins.cloud?.serverless.projectType} />
|
||||
</I18nProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaThemeProvider>,
|
||||
params.element
|
||||
);
|
||||
|
||||
return () => ReactDOM.unmountComponentAtNode(params.element);
|
||||
},
|
||||
});
|
||||
},
|
||||
start(coreStart, plugins) {
|
||||
// Register role switcher dropdown menu in the top right navigation of the Kibana UI
|
||||
coreStart.chrome.navControls.registerRight({
|
||||
order: 4000 + 1, // Make sure it comes after the user menu
|
||||
mount: (element: HTMLElement) => {
|
||||
ReactDOM.render(
|
||||
<KibanaThemeProvider theme={coreStart.theme}>
|
||||
<KibanaContextProvider services={coreStart}>
|
||||
<I18nProvider>
|
||||
<RoleSwitcher projectType={plugins.cloud?.serverless.projectType} />
|
||||
</I18nProvider>
|
||||
</KibanaContextProvider>
|
||||
</KibanaThemeProvider>,
|
||||
element
|
||||
);
|
||||
return () => ReactDOM.unmountComponentAtNode(element);
|
||||
},
|
||||
});
|
||||
},
|
||||
stop() {},
|
||||
});
|
50
packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx
Normal file
50
packages/kbn-mock-idp-plugin/public/reload_page_toast.tsx
Normal file
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 React from 'react';
|
||||
|
||||
import { toMountPoint } from '@kbn/react-kibana-mount';
|
||||
import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import type { ToastInput } from '@kbn/core-notifications-browser';
|
||||
import type { I18nStart } from '@kbn/core-i18n-browser';
|
||||
import type { ThemeServiceStart } from '@kbn/core-theme-browser';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin-types-common';
|
||||
|
||||
export const DATA_TEST_SUBJ_PAGE_RELOAD_BUTTON = 'pageReloadButton';
|
||||
|
||||
/**
|
||||
* Utility function for returning a {@link ToastInput} for displaying a prompt for reloading the page.
|
||||
* @param theme The {@link ThemeServiceStart} contract.
|
||||
* @param i18nStart The {@link I18nStart} contract.
|
||||
* @returns A toast.
|
||||
*/
|
||||
export const createReloadPageToast = (options: {
|
||||
user: Pick<AuthenticatedUser, 'roles'>;
|
||||
theme: ThemeServiceStart;
|
||||
i18n: I18nStart;
|
||||
}): ToastInput => {
|
||||
return {
|
||||
title: `Your role has been set to '${options.user.roles.join(`', '`)}'.`,
|
||||
text: toMountPoint(
|
||||
<EuiFlexGroup justifyContent="flexEnd" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
size="s"
|
||||
onClick={() => window.location.reload()}
|
||||
data-test-subj={DATA_TEST_SUBJ_PAGE_RELOAD_BUTTON}
|
||||
>
|
||||
Reload page
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>,
|
||||
{ i18n: options.i18n, theme: options.theme }
|
||||
),
|
||||
color: 'success',
|
||||
toastLifeTimeMs: 0x7fffffff, // Do not auto-hide toast since page is in an unknown state
|
||||
};
|
||||
};
|
168
packages/kbn-mock-idp-plugin/public/role_switcher.tsx
Normal file
168
packages/kbn-mock-idp-plugin/public/role_switcher.tsx
Normal file
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 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 React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
import { EuiButton, EuiPopover, EuiContextMenu } from '@elastic/eui';
|
||||
import { useKibana } from '@kbn/kibana-react-plugin/public';
|
||||
import { CoreStart } from '@kbn/core-lifecycle-browser';
|
||||
import useAsyncFn from 'react-use/lib/useAsyncFn';
|
||||
import type { AuthenticatedUser } from '@kbn/security-plugin-types-common';
|
||||
import {
|
||||
MOCK_IDP_REALM_NAME,
|
||||
MOCK_IDP_REALM_TYPE,
|
||||
MOCK_IDP_SECURITY_ROLE_NAMES,
|
||||
MOCK_IDP_OBSERVABILITY_ROLE_NAMES,
|
||||
MOCK_IDP_SEARCH_ROLE_NAMES,
|
||||
} from '@kbn/mock-idp-utils/src/constants';
|
||||
import { createReloadPageToast } from './reload_page_toast';
|
||||
import type { CreateSAMLResponseParams } from '../server';
|
||||
|
||||
const useCurrentUser = () => {
|
||||
const { services } = useKibana<CoreStart>();
|
||||
return useAsyncFn(() => services.http.get<AuthenticatedUser>('/internal/security/me'));
|
||||
};
|
||||
|
||||
export const useAuthenticator = (reloadPage = false) => {
|
||||
const { services } = useKibana<CoreStart>();
|
||||
|
||||
return useAsyncFn(async (params: CreateSAMLResponseParams) => {
|
||||
// Create SAML Response using Mock IDP
|
||||
const response = await services.http.post<Record<string, string>>('/mock_idp/saml_response', {
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
// Authenticate user with SAML response
|
||||
if (reloadPage) {
|
||||
const form = createForm('/api/security/saml/callback', response);
|
||||
form.submit();
|
||||
await new Promise(() => {}); // Never resolve
|
||||
} else {
|
||||
await services.http.post('/api/security/saml/callback', {
|
||||
body: JSON.stringify(response),
|
||||
asResponse: true,
|
||||
rawResponse: true,
|
||||
});
|
||||
}
|
||||
|
||||
return params;
|
||||
});
|
||||
};
|
||||
|
||||
export interface RoleSwitcherProps {
|
||||
projectType?: string;
|
||||
}
|
||||
|
||||
export const RoleSwitcher: FunctionComponent<RoleSwitcherProps> = ({ projectType }) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { services } = useKibana<CoreStart>();
|
||||
const [currentUserState, getCurrentUser] = useCurrentUser();
|
||||
const [authenticateUserState, authenticateUser] = useAuthenticator();
|
||||
|
||||
useEffect(() => {
|
||||
getCurrentUser();
|
||||
}, [getCurrentUser, authenticateUserState.value]);
|
||||
|
||||
useEffect(() => {
|
||||
if (authenticateUserState.value) {
|
||||
services.notifications.toasts.add(
|
||||
createReloadPageToast({
|
||||
user: authenticateUserState.value,
|
||||
theme: services.theme,
|
||||
i18n: services.i18n,
|
||||
})
|
||||
);
|
||||
}
|
||||
}, [authenticateUserState.value]); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
|
||||
if (!currentUserState.value || !isAuthenticatedWithMockIDP(currentUserState.value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const [currentRole] = currentUserState.value.roles;
|
||||
|
||||
const roles =
|
||||
projectType === 'security'
|
||||
? MOCK_IDP_SECURITY_ROLE_NAMES
|
||||
: projectType === 'observability'
|
||||
? MOCK_IDP_OBSERVABILITY_ROLE_NAMES
|
||||
: MOCK_IDP_SEARCH_ROLE_NAMES;
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
button={
|
||||
<EuiButton
|
||||
color="text"
|
||||
size="s"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
minWidth={false}
|
||||
onClick={() => setIsOpen((toggle) => !toggle)}
|
||||
isLoading={currentUserState.loading || authenticateUserState.loading}
|
||||
>
|
||||
{currentRole}
|
||||
</EuiButton>
|
||||
}
|
||||
panelPaddingSize="none"
|
||||
offset={4}
|
||||
anchorPosition="downRight"
|
||||
repositionOnScroll
|
||||
repositionToCrossAxis={false}
|
||||
isOpen={isOpen}
|
||||
closePopover={() => setIsOpen(false)}
|
||||
>
|
||||
<EuiContextMenu
|
||||
initialPanelId={0}
|
||||
panels={[
|
||||
{
|
||||
id: 0,
|
||||
title: 'Switch role',
|
||||
items: roles.map((role) => ({
|
||||
name: role,
|
||||
icon: currentUserState.value!.roles.includes(role) ? 'check' : 'empty',
|
||||
onClick: () => {
|
||||
authenticateUser({
|
||||
username: currentUserState.value!.username,
|
||||
full_name: currentUserState.value!.full_name,
|
||||
email: currentUserState.value!.email,
|
||||
roles: [role],
|
||||
});
|
||||
setIsOpen(false);
|
||||
},
|
||||
})),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</EuiPopover>
|
||||
);
|
||||
};
|
||||
|
||||
function isAuthenticatedWithMockIDP(user: AuthenticatedUser) {
|
||||
return (
|
||||
user.authentication_provider.type === MOCK_IDP_REALM_TYPE &&
|
||||
user.authentication_provider.name === MOCK_IDP_REALM_NAME
|
||||
);
|
||||
}
|
||||
|
||||
const createForm = (url: string, fields: Record<string, string>) => {
|
||||
const form = document.createElement('form');
|
||||
form.setAttribute('method', 'post');
|
||||
form.setAttribute('action', url);
|
||||
|
||||
for (const key in fields) {
|
||||
if (!fields.hasOwnProperty(key)) {
|
||||
continue;
|
||||
}
|
||||
const input = document.createElement('input');
|
||||
input.setAttribute('type', 'hidden');
|
||||
input.setAttribute('name', key);
|
||||
input.setAttribute('value', fields[key]);
|
||||
form.appendChild(input);
|
||||
}
|
||||
|
||||
return document.body.appendChild(form);
|
||||
};
|
|
@ -6,4 +6,5 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
export type { CreateSAMLResponseParams } from './plugin';
|
||||
export { plugin } from './plugin';
|
||||
|
|
|
@ -8,102 +8,59 @@
|
|||
|
||||
import type { PluginInitializer, Plugin } from '@kbn/core-plugins-server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import { MOCK_IDP_LOGIN_PATH, MOCK_IDP_LOGOUT_PATH, createSAMLResponse } from '@kbn/mock-idp-utils';
|
||||
|
||||
import {
|
||||
MOCK_IDP_LOGIN_PATH,
|
||||
MOCK_IDP_LOGOUT_PATH,
|
||||
createSAMLResponse,
|
||||
parseSAMLAuthnRequest,
|
||||
} from '../common';
|
||||
const createSAMLResponseSchema = schema.object({
|
||||
username: schema.string(),
|
||||
full_name: schema.maybe(schema.nullable(schema.string())),
|
||||
email: schema.maybe(schema.nullable(schema.string())),
|
||||
roles: schema.arrayOf(schema.string()),
|
||||
});
|
||||
|
||||
export type CreateSAMLResponseParams = TypeOf<typeof createSAMLResponseSchema>;
|
||||
|
||||
export const plugin: PluginInitializer<void, void> = async (): Promise<Plugin> => ({
|
||||
setup(core) {
|
||||
const router = core.http.createRouter();
|
||||
|
||||
core.http.resources.register(
|
||||
{
|
||||
path: MOCK_IDP_LOGIN_PATH,
|
||||
validate: false,
|
||||
options: { authRequired: false },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return response.renderAnonymousCoreApp();
|
||||
}
|
||||
);
|
||||
|
||||
router.post(
|
||||
{
|
||||
path: '/mock_idp/saml_response',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
SAMLRequest: schema.string(),
|
||||
}),
|
||||
body: createSAMLResponseSchema,
|
||||
},
|
||||
options: { authRequired: false },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
let samlRequest: Awaited<ReturnType<typeof parseSAMLAuthnRequest>>;
|
||||
try {
|
||||
samlRequest = await parseSAMLAuthnRequest(request.query.SAMLRequest);
|
||||
} catch (error) {
|
||||
return response.badRequest({
|
||||
body: '[request query.SAMLRequest]: value is not valid SAMLRequest.',
|
||||
});
|
||||
}
|
||||
const { protocol, hostname, port } = core.http.getServerInfo();
|
||||
const pathname = core.http.basePath.prepend('/api/security/saml/callback');
|
||||
|
||||
const userRoles: Array<[string, string]> = [
|
||||
['system_indices_superuser', 'system_indices_superuser'],
|
||||
['t1_analyst', 't1_analyst'],
|
||||
['t2_analyst', 't2_analyst'],
|
||||
['t3_analyst', 't3_analyst'],
|
||||
['threat_intelligence_analyst', 'threat_intelligence_analyst'],
|
||||
['rule_author', 'rule_author'],
|
||||
['soc_manager', 'soc_manager'],
|
||||
['detections_admin', 'detections_admin'],
|
||||
['platform_engineer', 'platform_engineer'],
|
||||
['endpoint_operations_analyst', 'endpoint_operations_analyst'],
|
||||
['endpoint_policy_manager', 'endpoint_policy_manager'],
|
||||
];
|
||||
|
||||
const samlResponses = await Promise.all(
|
||||
userRoles.map(([username, role]) =>
|
||||
createSAMLResponse({
|
||||
authnRequestId: samlRequest.ID,
|
||||
kibanaUrl: samlRequest.AssertionConsumerServiceURL,
|
||||
username,
|
||||
roles: [role],
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return response.renderHtml({
|
||||
body: `
|
||||
<!DOCTYPE html>
|
||||
<title>Mock Identity Provider</title>
|
||||
<link rel="icon" href="data:,">
|
||||
<body>
|
||||
<h2>Mock Identity Provider</h2>
|
||||
<form id="loginForm" method="post" action="${
|
||||
samlRequest.AssertionConsumerServiceURL
|
||||
}">
|
||||
<h3>Pick a role:<h3>
|
||||
<ul>
|
||||
${userRoles
|
||||
.map(
|
||||
([username], i) =>
|
||||
`
|
||||
<li>
|
||||
<button name="SAMLResponse" value="${samlResponses[i]}">${username}</button>
|
||||
</li>
|
||||
`
|
||||
)
|
||||
.join('')}
|
||||
</ul>
|
||||
</form>
|
||||
</body>
|
||||
`,
|
||||
return response.ok({
|
||||
body: {
|
||||
SAMLResponse: await createSAMLResponse({
|
||||
kibanaUrl: `${protocol}://${hostname}:${port}${pathname}`,
|
||||
username: request.body.username,
|
||||
full_name: request.body.full_name ?? undefined,
|
||||
email: request.body.email ?? undefined,
|
||||
roles: request.body.roles,
|
||||
}),
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
core.http.resources.register(
|
||||
{
|
||||
path: `${MOCK_IDP_LOGIN_PATH}/submit.js`,
|
||||
validate: false,
|
||||
options: { authRequired: false },
|
||||
},
|
||||
(context, request, response) => {
|
||||
return response.renderJs({ body: 'document.getElementById("loginForm").submit();' });
|
||||
}
|
||||
);
|
||||
|
||||
core.http.resources.register(
|
||||
{
|
||||
path: MOCK_IDP_LOGOUT_PATH,
|
||||
|
|
|
@ -11,8 +11,20 @@
|
|||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/core-plugins-server",
|
||||
"@kbn/config-schema",
|
||||
"@kbn/dev-utils"
|
||||
"@kbn/core-application-browser",
|
||||
"@kbn/core-i18n-browser",
|
||||
"@kbn/core-lifecycle-browser",
|
||||
"@kbn/core-notifications-browser",
|
||||
"@kbn/core-plugins-browser",
|
||||
"@kbn/core-plugins-server",
|
||||
"@kbn/core-theme-browser",
|
||||
"@kbn/i18n-react",
|
||||
"@kbn/kibana-react-plugin",
|
||||
"@kbn/react-kibana-context-theme",
|
||||
"@kbn/react-kibana-mount",
|
||||
"@kbn/security-plugin-types-common",
|
||||
"@kbn/mock-idp-utils",
|
||||
"@kbn/cloud-plugin",
|
||||
]
|
||||
}
|
||||
|
|
9
packages/kbn-mock-idp-utils/index.ts
Normal file
9
packages/kbn-mock-idp-utils/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
/*
|
||||
* 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 * from './src';
|
6
packages/kbn-mock-idp-utils/kibana.jsonc
Normal file
6
packages/kbn-mock-idp-utils/kibana.jsonc
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"type": "shared-common",
|
||||
"id": "@kbn/mock-idp-utils",
|
||||
"owner": "@elastic/kibana-security",
|
||||
"devOnly": true,
|
||||
}
|
6
packages/kbn-mock-idp-utils/package.json
Normal file
6
packages/kbn-mock-idp-utils/package.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@kbn/mock-idp-utils",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"license": "SSPL-1.0 OR Elastic License 2.0"
|
||||
}
|
|
@ -6,15 +6,11 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { resolve } from 'path';
|
||||
|
||||
export const MOCK_IDP_PLUGIN_PATH = resolve(__dirname, '..');
|
||||
export const MOCK_IDP_METADATA_PATH = resolve(MOCK_IDP_PLUGIN_PATH, 'metadata.xml');
|
||||
|
||||
export const MOCK_IDP_LOGIN_PATH = '/mock_idp/login';
|
||||
export const MOCK_IDP_LOGOUT_PATH = '/mock_idp/logout';
|
||||
|
||||
export const MOCK_IDP_REALM_NAME = 'mock-idp';
|
||||
export const MOCK_IDP_REALM_TYPE = 'saml';
|
||||
export const MOCK_IDP_ENTITY_ID = 'urn:mock-idp'; // Must match `entityID` in `metadata.xml`
|
||||
export const MOCK_IDP_ROLE_MAPPING_NAME = 'mock-idp-mapping';
|
||||
|
||||
|
@ -22,3 +18,20 @@ export const MOCK_IDP_ATTRIBUTE_PRINCIPAL = 'http://saml.elastic-cloud.com/attri
|
|||
export const MOCK_IDP_ATTRIBUTE_ROLES = 'http://saml.elastic-cloud.com/attributes/roles';
|
||||
export const MOCK_IDP_ATTRIBUTE_EMAIL = 'http://saml.elastic-cloud.com/attributes/email';
|
||||
export const MOCK_IDP_ATTRIBUTE_NAME = 'http://saml.elastic-cloud.com/attributes/name';
|
||||
|
||||
/** List of roles from `packages/kbn-es/src/serverless_resources/roles.yml` */
|
||||
export const MOCK_IDP_SEARCH_ROLE_NAMES = ['viewer', 'editor', 'system_indices_superuser'];
|
||||
export const MOCK_IDP_OBSERVABILITY_ROLE_NAMES = ['viewer', 'editor', 'system_indices_superuser'];
|
||||
export const MOCK_IDP_SECURITY_ROLE_NAMES = [
|
||||
't1_analyst',
|
||||
't2_analyst',
|
||||
't3_analyst',
|
||||
'threat_intelligence_analyst',
|
||||
'rule_author',
|
||||
'soc_manager',
|
||||
'detections_admin',
|
||||
'platform_engineer',
|
||||
'endpoint_operations_analyst',
|
||||
'endpoint_policy_manager',
|
||||
'system_indices_superuser',
|
||||
];
|
25
packages/kbn-mock-idp-utils/src/index.ts
Normal file
25
packages/kbn-mock-idp-utils/src/index.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* 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 {
|
||||
MOCK_IDP_LOGIN_PATH,
|
||||
MOCK_IDP_LOGOUT_PATH,
|
||||
MOCK_IDP_REALM_NAME,
|
||||
MOCK_IDP_REALM_TYPE,
|
||||
MOCK_IDP_ENTITY_ID,
|
||||
MOCK_IDP_ROLE_MAPPING_NAME,
|
||||
MOCK_IDP_ATTRIBUTE_PRINCIPAL,
|
||||
MOCK_IDP_ATTRIBUTE_ROLES,
|
||||
MOCK_IDP_ATTRIBUTE_EMAIL,
|
||||
MOCK_IDP_ATTRIBUTE_NAME,
|
||||
MOCK_IDP_SEARCH_ROLE_NAMES,
|
||||
MOCK_IDP_OBSERVABILITY_ROLE_NAMES,
|
||||
MOCK_IDP_SECURITY_ROLE_NAMES,
|
||||
} from './constants';
|
||||
|
||||
export { createMockIdpMetadata, createSAMLResponse, ensureSAMLRoleMapping } from './utils';
|
|
@ -7,12 +7,10 @@
|
|||
*/
|
||||
|
||||
import { Client } from '@elastic/elasticsearch';
|
||||
|
||||
import { SignedXml } from 'xml-crypto';
|
||||
import { KBN_KEY_PATH, KBN_CERT_PATH } from '@kbn/dev-utils';
|
||||
import { readFile } from 'fs/promises';
|
||||
import zlib from 'zlib';
|
||||
import { promisify } from 'util';
|
||||
import { parseString } from 'xml2js';
|
||||
import { X509Certificate } from 'crypto';
|
||||
|
||||
import {
|
||||
|
@ -27,9 +25,6 @@ import {
|
|||
MOCK_IDP_LOGOUT_PATH,
|
||||
} from './constants';
|
||||
|
||||
const inflateRawAsync = promisify(zlib.inflateRaw);
|
||||
const parseStringAsync = promisify(parseString);
|
||||
|
||||
/**
|
||||
* Creates XML metadata for our mock identity provider.
|
||||
*
|
||||
|
@ -76,7 +71,7 @@ export async function createMockIdpMetadata(kibanaUrl: string) {
|
|||
* const samlResponse = await createSAMLResponse({
|
||||
* username: '1234567890',
|
||||
* email: 'mail@elastic.co',
|
||||
* fullname: 'Test User',
|
||||
* full_nname: 'Test User',
|
||||
* roles: ['t1_analyst', 'editor'],
|
||||
* })
|
||||
* ```
|
||||
|
@ -87,7 +82,6 @@ export async function createMockIdpMetadata(kibanaUrl: string) {
|
|||
* fetch('/api/security/saml/callback', {
|
||||
* method: 'POST',
|
||||
* body: JSON.stringify({ SAMLResponse: samlResponse }),
|
||||
* redirect: 'manual'
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
|
@ -97,7 +91,7 @@ export async function createSAMLResponse(options: {
|
|||
/** ID from SAML authentication request */
|
||||
authnRequestId?: string;
|
||||
username: string;
|
||||
fullname?: string;
|
||||
full_name?: string;
|
||||
email?: string;
|
||||
roles: string[];
|
||||
}) {
|
||||
|
@ -139,9 +133,9 @@ export async function createSAMLResponse(options: {
|
|||
: ''
|
||||
}
|
||||
${
|
||||
options.fullname
|
||||
options.full_name
|
||||
? `<saml:Attribute Name="${MOCK_IDP_ATTRIBUTE_NAME}" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
|
||||
<saml:AttributeValue xsi:type="xs:string">${options.fullname}</saml:AttributeValue>
|
||||
<saml:AttributeValue xsi:type="xs:string">${options.full_name}</saml:AttributeValue>
|
||||
</saml:Attribute>`
|
||||
: ''
|
||||
}
|
||||
|
@ -210,22 +204,3 @@ export async function ensureSAMLRoleMapping(client: Client) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
interface SAMLAuthnRequest {
|
||||
'saml2p:AuthnRequest': {
|
||||
$: {
|
||||
AssertionConsumerServiceURL: string;
|
||||
Destination: string;
|
||||
ID: string;
|
||||
IssueInstant: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export async function parseSAMLAuthnRequest(samlRequest: string) {
|
||||
const inflatedSAMLRequest = (await inflateRawAsync(Buffer.from(samlRequest, 'base64'))) as Buffer;
|
||||
const parsedSAMLRequest = (await parseStringAsync(
|
||||
inflatedSAMLRequest.toString()
|
||||
)) as SAMLAuthnRequest;
|
||||
return parsedSAMLRequest['saml2p:AuthnRequest'].$;
|
||||
}
|
16
packages/kbn-mock-idp-utils/tsconfig.json
Normal file
16
packages/kbn-mock-idp-utils/tsconfig.json
Normal file
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "target/types"
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx"
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*"
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/dev-utils",
|
||||
]
|
||||
}
|
|
@ -98,6 +98,7 @@ pageLoadAssetSize:
|
|||
mapsEms: 26072
|
||||
metricsDataAccess: 73287
|
||||
ml: 82187
|
||||
mockIdpPlugin: 30000
|
||||
monitoring: 80000
|
||||
navigation: 37269
|
||||
newsfeed: 42228
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { createSAMLResponse as createMockedSAMLResponse } from '@kbn/mock-idp-plugin/common';
|
||||
import { createSAMLResponse as createMockedSAMLResponse } from '@kbn/mock-idp-utils';
|
||||
import { ToolingLog } from '@kbn/tooling-log';
|
||||
import axios, { AxiosResponse } from 'axios';
|
||||
import * as cheerio from 'cheerio';
|
||||
|
@ -217,7 +217,7 @@ export const createLocalSAMLSession = async (params: LocalSamlSessionParams) =>
|
|||
const samlResponse = await createMockedSAMLResponse({
|
||||
kibanaUrl: kbnHost + '/api/security/saml/callback',
|
||||
username,
|
||||
fullname,
|
||||
full_name: fullname,
|
||||
email,
|
||||
roles: [role],
|
||||
});
|
||||
|
|
|
@ -3,7 +3,10 @@
|
|||
"compilerOptions": {
|
||||
"outDir": "target/types",
|
||||
"stripInternal": true,
|
||||
"types": ["jest", "node"]
|
||||
"types": [
|
||||
"jest",
|
||||
"node"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
|
@ -32,7 +35,7 @@
|
|||
"@kbn/babel-register",
|
||||
"@kbn/repo-packages",
|
||||
"@kbn/core-saved-objects-api-server",
|
||||
"@kbn/mock-idp-plugin",
|
||||
"@kbn/mock-idp-utils",
|
||||
"@kbn/code-owners",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -18,6 +18,8 @@ import { getConfigFromFiles } from '@kbn/config';
|
|||
|
||||
const DEV_MODE_PATH = '@kbn/cli-dev-mode';
|
||||
const DEV_MODE_SUPPORTED = canRequire(DEV_MODE_PATH);
|
||||
const MOCK_IDP_PLUGIN_PATH = '@kbn/mock-idp-plugin/common';
|
||||
const MOCK_IDP_PLUGIN_SUPPORTED = canRequire(MOCK_IDP_PLUGIN_PATH);
|
||||
|
||||
function canRequire(path) {
|
||||
try {
|
||||
|
@ -111,12 +113,11 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
|
|||
setServerlessKibanaDevServiceAccountIfPossible(get, set, opts);
|
||||
|
||||
// Load mock identity provider plugin and configure realm if supported (ES only supports SAML when run with SSL)
|
||||
if (opts.ssl && canRequire('@kbn/mock-idp-plugin/common')) {
|
||||
if (opts.ssl && MOCK_IDP_PLUGIN_SUPPORTED) {
|
||||
// Ensure the plugin is loaded in dynamically to exclude from production build
|
||||
const {
|
||||
MOCK_IDP_PLUGIN_PATH,
|
||||
MOCK_IDP_REALM_NAME,
|
||||
} = require('@kbn/mock-idp-plugin/common');
|
||||
// eslint-disable-next-line import/no-dynamic-require
|
||||
const { MOCK_IDP_REALM_NAME } = require(MOCK_IDP_PLUGIN_PATH);
|
||||
const pluginPath = resolve(require.resolve(MOCK_IDP_PLUGIN_PATH), '..');
|
||||
|
||||
if (has('server.basePath')) {
|
||||
console.log(
|
||||
|
@ -125,7 +126,7 @@ export function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
|
|||
_.unset(rawConfig, 'server.basePath');
|
||||
}
|
||||
|
||||
set('plugins.paths', _.compact([].concat(get('plugins.paths'), MOCK_IDP_PLUGIN_PATH)));
|
||||
set('plugins.paths', _.compact([].concat(get('plugins.paths'), pluginPath)));
|
||||
set(`xpack.security.authc.providers.saml.${MOCK_IDP_REALM_NAME}`, {
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
realm: MOCK_IDP_REALM_NAME,
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
"@kbn/config",
|
||||
"@kbn/dev-utils",
|
||||
"@kbn/apm-config-loader",
|
||||
"@kbn/mock-idp-plugin",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -246,7 +246,7 @@ export class Config {
|
|||
return getPackages(this.repoRoot).filter(
|
||||
(p) =>
|
||||
(this.pluginSelector.testPlugins || !p.isDevOnly()) &&
|
||||
(!p.isPlugin() || this.pluginFilter(p))
|
||||
(!p.isPlugin() || (this.pluginFilter(p) && !p.isDevOnly()))
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1100,6 +1100,8 @@
|
|||
"@kbn/ml-url-state/*": ["x-pack/packages/ml/url_state/*"],
|
||||
"@kbn/mock-idp-plugin": ["packages/kbn-mock-idp-plugin"],
|
||||
"@kbn/mock-idp-plugin/*": ["packages/kbn-mock-idp-plugin/*"],
|
||||
"@kbn/mock-idp-utils": ["packages/kbn-mock-idp-utils"],
|
||||
"@kbn/mock-idp-utils/*": ["packages/kbn-mock-idp-utils/*"],
|
||||
"@kbn/monaco": ["packages/kbn-monaco"],
|
||||
"@kbn/monaco/*": ["packages/kbn-monaco/*"],
|
||||
"@kbn/monitoring-collection-plugin": ["x-pack/plugins/monitoring_collection"],
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
"plugin": {
|
||||
"id": "samlProviderPlugin",
|
||||
"server": true,
|
||||
"browser": false
|
||||
"browser": false,
|
||||
"optionalPlugins": [
|
||||
"cloud"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,10 +6,17 @@
|
|||
*/
|
||||
|
||||
import type { PluginInitializer, Plugin } from '@kbn/core/server';
|
||||
import { CloudSetup } from '@kbn/cloud-plugin/server';
|
||||
import { initRoutes } from './init_routes';
|
||||
|
||||
export const plugin: PluginInitializer<void, void> = async (): Promise<Plugin> => ({
|
||||
setup: (core) => initRoutes(core),
|
||||
export interface PluginSetupDependencies {
|
||||
cloud?: CloudSetup;
|
||||
}
|
||||
|
||||
export const plugin: PluginInitializer<void, void, PluginSetupDependencies> = async (
|
||||
context
|
||||
): Promise<Plugin> => ({
|
||||
setup: (core, plugins: PluginSetupDependencies) => initRoutes(context, core, plugins),
|
||||
start: () => {},
|
||||
stop: () => {},
|
||||
});
|
||||
|
|
|
@ -5,13 +5,18 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { CoreSetup } from '@kbn/core/server';
|
||||
import { CoreSetup, PluginInitializerContext } from '@kbn/core/server';
|
||||
import {
|
||||
getSAMLResponse,
|
||||
getSAMLRequestId,
|
||||
} from '@kbn/security-api-integration-helpers/saml/saml_tools';
|
||||
import { PluginSetupDependencies } from '.';
|
||||
|
||||
export function initRoutes(core: CoreSetup) {
|
||||
export function initRoutes(
|
||||
pluginContext: PluginInitializerContext,
|
||||
core: CoreSetup,
|
||||
plugins: PluginSetupDependencies
|
||||
) {
|
||||
const serverInfo = core.http.getServerInfo();
|
||||
core.http.resources.register(
|
||||
{
|
||||
|
@ -59,6 +64,26 @@ export function initRoutes(core: CoreSetup) {
|
|||
}
|
||||
);
|
||||
|
||||
// [HACK]: On CI, Kibana runs Serverless functional tests against the production Kibana build but still relies on Mock
|
||||
// IdP for SAML authentication in tests. The Mock IdP SAML realm, in turn, is linked to a Mock IDP plugin in Kibana
|
||||
// that's only included in development mode and not available in the production Kibana build. Until our testing
|
||||
// framework can properly support all SAML flows, we should forward all relevant Mock IDP plugin endpoints to a logout
|
||||
// destination normally used in the Serverless setup.
|
||||
if (pluginContext.env.mode.prod) {
|
||||
core.http.resources.register(
|
||||
{
|
||||
path: '/mock_idp/login',
|
||||
validate: false,
|
||||
options: { authRequired: false },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
return response.redirected({
|
||||
headers: { location: plugins.cloud?.projectsUrl ?? '/login' },
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
let attemptsCounter = 0;
|
||||
core.http.resources.register(
|
||||
{
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
"target/**/*",
|
||||
],
|
||||
"kbn_references": [
|
||||
"@kbn/cloud-plugin",
|
||||
"@kbn/core",
|
||||
"@kbn/security-api-integration-helpers",
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ import expect from '@kbn/expect';
|
|||
import { parse as parseCookie } from 'tough-cookie';
|
||||
import Url from 'url';
|
||||
|
||||
import { createSAMLResponse } from '@kbn/mock-idp-plugin/common';
|
||||
import { createSAMLResponse } from '@kbn/mock-idp-utils';
|
||||
import { FtrProviderContext } from '../ftr_provider_context';
|
||||
|
||||
export function SamlToolsProvider({ getService }: FtrProviderContext) {
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
} from '@kbn/test';
|
||||
import { CA_CERT_PATH, kibanaDevServiceAccount } from '@kbn/dev-utils';
|
||||
import { commonFunctionalServices } from '@kbn/ftr-common-functional-services';
|
||||
import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-plugin/common';
|
||||
import { MOCK_IDP_REALM_NAME } from '@kbn/mock-idp-utils';
|
||||
import { services } from './services';
|
||||
|
||||
export default async () => {
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
"@kbn/apm-synthtrace",
|
||||
"@kbn/apm-synthtrace-client",
|
||||
"@kbn/reporting-export-types-csv-common",
|
||||
"@kbn/mock-idp-plugin",
|
||||
"@kbn/mock-idp-utils",
|
||||
"@kbn/io-ts-utils",
|
||||
"@kbn/log-explorer-plugin",
|
||||
"@kbn/index-management-plugin",
|
||||
|
|
|
@ -5260,6 +5260,10 @@
|
|||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/mock-idp-utils@link:packages/kbn-mock-idp-utils":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
||||
"@kbn/monaco@link:packages/kbn-monaco":
|
||||
version "0.0.0"
|
||||
uid ""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue