mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[Http] Replace buildNr
with buildSha
in static asset paths (#175898)
## Summary Follow up of [first CDN PR](https://github.com/elastic/kibana/pull/169408). Primary focus is replacing our build nr with SHA that allows cache busting and maintains anti-collision properties. ## How to test Start Kibana as usual navigating around the app with the network tab open in your browser of choice. Keep an eye out for any asset loading errors. It's tricky to test every possible asset since there are many permutations, but generally navigating around Kibana should work exactly as it did before regarding loading bundles and assets. ## Notes * did a high-level audit of usages of `buildNum` in `packages`, `src` and `x-pack` adding comments where appropriate. * In non-distributable builds (like dev) static asset paths will be prefixed with `XXXXXXXXXXXX` instead of Node's `Number.MAX_SAFE_INTEGER` * Added some validation to ensure the CDN url is of the expected form: nothing trailing the pathname ### Checklist Delete any items that are not applicable to this PR. - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios ### Risk Matrix | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | We break some first or third party dependencies on existing asset routes | Med | High | Attempting to mitgate by serving static assets from both old and new paths where paths have updated to include the build SHA. Additioanlly: it is very bad practice to rely on the values of the static paths, but someone might be | | Cache-busting is more aggressive | High | Low | Unlikely to be a big problem, but we are not scoping more static assets to a SHA and so every new Kibana release will require clients to, for example, download fonts again. | ### For maintainers - [ ] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
455ee0a450
commit
e90c2098f2
63 changed files with 685 additions and 243 deletions
|
@ -30,7 +30,7 @@ const PAGE_VARS_KEYS = [
|
|||
|
||||
// Deployment-specific keys
|
||||
'version', // x4, split to version_major, version_minor, version_patch for easier filtering
|
||||
'buildNum', // May be useful for Serverless
|
||||
'buildNum', // May be useful for Serverless, TODO: replace with buildHash
|
||||
'cloudId',
|
||||
'deploymentId',
|
||||
'projectId', // projectId and deploymentId are mutually exclusive. They shouldn't be sent in the same offering.
|
||||
|
|
|
@ -13,10 +13,12 @@ import { httpServiceMock } from '@kbn/core-http-server-mocks';
|
|||
import type { InternalPluginInfo, UiPlugins } from '@kbn/core-plugins-base-server-internal';
|
||||
import { registerBundleRoutes } from './register_bundle_routes';
|
||||
import { FileHashCache } from './file_hash_cache';
|
||||
import { BasePath, StaticAssets } from '@kbn/core-http-server-internal';
|
||||
|
||||
const createPackageInfo = (parts: Partial<PackageInfo> = {}): PackageInfo => ({
|
||||
buildNum: 42,
|
||||
buildSha: 'sha',
|
||||
buildSha: 'shasha',
|
||||
buildShaShort: 'sha',
|
||||
dist: true,
|
||||
branch: 'master',
|
||||
version: '8.0.0',
|
||||
|
@ -41,9 +43,12 @@ const createUiPlugins = (...ids: string[]): UiPlugins => ({
|
|||
|
||||
describe('registerBundleRoutes', () => {
|
||||
let router: ReturnType<typeof httpServiceMock.createRouter>;
|
||||
let staticAssets: StaticAssets;
|
||||
|
||||
beforeEach(() => {
|
||||
router = httpServiceMock.createRouter();
|
||||
const basePath = httpServiceMock.createBasePath('/server-base-path') as unknown as BasePath;
|
||||
staticAssets = new StaticAssets({ basePath, cdnConfig: {} as any, shaDigest: 'sha' });
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
|
@ -53,7 +58,7 @@ describe('registerBundleRoutes', () => {
|
|||
it('registers core and shared-dep bundles', () => {
|
||||
registerBundleRoutes({
|
||||
router,
|
||||
serverBasePath: '/server-base-path',
|
||||
staticAssets,
|
||||
packageInfo: createPackageInfo(),
|
||||
uiPlugins: createUiPlugins(),
|
||||
});
|
||||
|
@ -64,39 +69,39 @@ describe('registerBundleRoutes', () => {
|
|||
fileHashCache: expect.any(FileHashCache),
|
||||
isDist: true,
|
||||
bundlesPath: 'uiSharedDepsSrcDistDir',
|
||||
publicPath: '/server-base-path/42/bundles/kbn-ui-shared-deps-src/',
|
||||
routePath: '/42/bundles/kbn-ui-shared-deps-src/',
|
||||
publicPath: '/server-base-path/sha/bundles/kbn-ui-shared-deps-src/',
|
||||
routePath: '/sha/bundles/kbn-ui-shared-deps-src/',
|
||||
});
|
||||
|
||||
expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, {
|
||||
fileHashCache: expect.any(FileHashCache),
|
||||
isDist: true,
|
||||
bundlesPath: 'uiSharedDepsNpmDistDir',
|
||||
publicPath: '/server-base-path/42/bundles/kbn-ui-shared-deps-npm/',
|
||||
routePath: '/42/bundles/kbn-ui-shared-deps-npm/',
|
||||
publicPath: '/server-base-path/sha/bundles/kbn-ui-shared-deps-npm/',
|
||||
routePath: '/sha/bundles/kbn-ui-shared-deps-npm/',
|
||||
});
|
||||
|
||||
expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, {
|
||||
fileHashCache: expect.any(FileHashCache),
|
||||
isDist: true,
|
||||
bundlesPath: expect.stringMatching(/\/@kbn\/core\/target\/public$/),
|
||||
publicPath: '/server-base-path/42/bundles/core/',
|
||||
routePath: '/42/bundles/core/',
|
||||
publicPath: '/server-base-path/sha/bundles/core/',
|
||||
routePath: '/sha/bundles/core/',
|
||||
});
|
||||
|
||||
expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, {
|
||||
fileHashCache: expect.any(FileHashCache),
|
||||
isDist: true,
|
||||
bundlesPath: 'kbnMonacoBundleDir',
|
||||
publicPath: '/server-base-path/42/bundles/kbn-monaco/',
|
||||
routePath: '/42/bundles/kbn-monaco/',
|
||||
publicPath: '/server-base-path/sha/bundles/kbn-monaco/',
|
||||
routePath: '/sha/bundles/kbn-monaco/',
|
||||
});
|
||||
});
|
||||
|
||||
it('registers plugin bundles', () => {
|
||||
registerBundleRoutes({
|
||||
router,
|
||||
serverBasePath: '/server-base-path',
|
||||
staticAssets,
|
||||
packageInfo: createPackageInfo(),
|
||||
uiPlugins: createUiPlugins('plugin-a', 'plugin-b'),
|
||||
});
|
||||
|
@ -107,16 +112,16 @@ describe('registerBundleRoutes', () => {
|
|||
fileHashCache: expect.any(FileHashCache),
|
||||
isDist: true,
|
||||
bundlesPath: '/plugins/plugin-a/public-target-dir',
|
||||
publicPath: '/server-base-path/42/bundles/plugin/plugin-a/8.0.0/',
|
||||
routePath: '/42/bundles/plugin/plugin-a/8.0.0/',
|
||||
publicPath: '/server-base-path/sha/bundles/plugin/plugin-a/8.0.0/',
|
||||
routePath: '/sha/bundles/plugin/plugin-a/8.0.0/',
|
||||
});
|
||||
|
||||
expect(registerRouteForBundleMock).toHaveBeenCalledWith(router, {
|
||||
fileHashCache: expect.any(FileHashCache),
|
||||
isDist: true,
|
||||
bundlesPath: '/plugins/plugin-b/public-target-dir',
|
||||
publicPath: '/server-base-path/42/bundles/plugin/plugin-b/8.0.0/',
|
||||
routePath: '/42/bundles/plugin/plugin-b/8.0.0/',
|
||||
publicPath: '/server-base-path/sha/bundles/plugin/plugin-b/8.0.0/',
|
||||
routePath: '/sha/bundles/plugin/plugin-b/8.0.0/',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ import { distDir as UiSharedDepsSrcDistDir } from '@kbn/ui-shared-deps-src';
|
|||
import * as KbnMonaco from '@kbn/monaco/server';
|
||||
import type { IRouter } from '@kbn/core-http-server';
|
||||
import type { UiPlugins } from '@kbn/core-plugins-base-server-internal';
|
||||
import { InternalStaticAssets } from '@kbn/core-http-server-internal';
|
||||
import { FileHashCache } from './file_hash_cache';
|
||||
import { registerRouteForBundle } from './bundles_route';
|
||||
|
||||
|
@ -28,56 +29,61 @@ import { registerRouteForBundle } from './bundles_route';
|
|||
*/
|
||||
export function registerBundleRoutes({
|
||||
router,
|
||||
serverBasePath,
|
||||
uiPlugins,
|
||||
packageInfo,
|
||||
staticAssets,
|
||||
}: {
|
||||
router: IRouter;
|
||||
serverBasePath: string;
|
||||
uiPlugins: UiPlugins;
|
||||
packageInfo: PackageInfo;
|
||||
staticAssets: InternalStaticAssets;
|
||||
}) {
|
||||
const { dist: isDist, buildNum } = packageInfo;
|
||||
const { dist: isDist } = packageInfo;
|
||||
// rather than calculate the fileHash on every request, we
|
||||
// provide a cache object to `resolveDynamicAssetResponse()` that
|
||||
// will store the most recently used hashes.
|
||||
const fileHashCache = new FileHashCache();
|
||||
|
||||
const sharedNpmDepsPath = '/bundles/kbn-ui-shared-deps-npm/';
|
||||
registerRouteForBundle(router, {
|
||||
publicPath: `${serverBasePath}/${buildNum}/bundles/kbn-ui-shared-deps-npm/`,
|
||||
routePath: `/${buildNum}/bundles/kbn-ui-shared-deps-npm/`,
|
||||
publicPath: staticAssets.prependPublicUrl(sharedNpmDepsPath) + '/',
|
||||
routePath: staticAssets.prependServerPath(sharedNpmDepsPath) + '/',
|
||||
bundlesPath: UiSharedDepsNpm.distDir,
|
||||
fileHashCache,
|
||||
isDist,
|
||||
});
|
||||
const sharedDepsPath = '/bundles/kbn-ui-shared-deps-src/';
|
||||
registerRouteForBundle(router, {
|
||||
publicPath: `${serverBasePath}/${buildNum}/bundles/kbn-ui-shared-deps-src/`,
|
||||
routePath: `/${buildNum}/bundles/kbn-ui-shared-deps-src/`,
|
||||
publicPath: staticAssets.prependPublicUrl(sharedDepsPath) + '/',
|
||||
routePath: staticAssets.prependServerPath(sharedDepsPath) + '/',
|
||||
bundlesPath: UiSharedDepsSrcDistDir,
|
||||
fileHashCache,
|
||||
isDist,
|
||||
});
|
||||
const coreBundlePath = '/bundles/core/';
|
||||
registerRouteForBundle(router, {
|
||||
publicPath: `${serverBasePath}/${buildNum}/bundles/core/`,
|
||||
routePath: `/${buildNum}/bundles/core/`,
|
||||
publicPath: staticAssets.prependPublicUrl(coreBundlePath) + '/',
|
||||
routePath: staticAssets.prependServerPath(coreBundlePath) + '/',
|
||||
bundlesPath: isDist
|
||||
? fromRoot('node_modules/@kbn/core/target/public')
|
||||
: fromRoot('src/core/target/public'),
|
||||
fileHashCache,
|
||||
isDist,
|
||||
});
|
||||
const monacoEditorPath = '/bundles/kbn-monaco/';
|
||||
registerRouteForBundle(router, {
|
||||
publicPath: `${serverBasePath}/${buildNum}/bundles/kbn-monaco/`,
|
||||
routePath: `/${buildNum}/bundles/kbn-monaco/`,
|
||||
publicPath: staticAssets.prependPublicUrl(monacoEditorPath) + '/',
|
||||
routePath: staticAssets.prependServerPath(monacoEditorPath) + '/',
|
||||
bundlesPath: KbnMonaco.bundleDir,
|
||||
fileHashCache,
|
||||
isDist,
|
||||
});
|
||||
|
||||
[...uiPlugins.internal.entries()].forEach(([id, { publicTargetDir, version }]) => {
|
||||
const pluginBundlesPath = `/bundles/plugin/${id}/${version}/`;
|
||||
registerRouteForBundle(router, {
|
||||
publicPath: `${serverBasePath}/${buildNum}/bundles/plugin/${id}/${version}/`,
|
||||
routePath: `/${buildNum}/bundles/plugin/${id}/${version}/`,
|
||||
publicPath: staticAssets.prependPublicUrl(pluginBundlesPath) + '/',
|
||||
routePath: staticAssets.prependServerPath(pluginBundlesPath) + '/',
|
||||
bundlesPath: publicTargetDir,
|
||||
fileHashCache,
|
||||
isDist,
|
||||
|
|
|
@ -16,8 +16,8 @@ import { httpResourcesMock } from '@kbn/core-http-resources-server-mocks';
|
|||
import { PluginType } from '@kbn/core-base-common';
|
||||
import type { RequestHandlerContext } from '@kbn/core-http-request-handler-context-server';
|
||||
import { coreInternalLifecycleMock } from '@kbn/core-lifecycle-server-mocks';
|
||||
import { CoreAppsService } from './core_app';
|
||||
import { of } from 'rxjs';
|
||||
import { CoreAppsService } from './core_app';
|
||||
|
||||
const emptyPlugins = (): UiPlugins => ({
|
||||
internal: new Map(),
|
||||
|
@ -146,7 +146,7 @@ describe('CoreApp', () => {
|
|||
uiPlugins: prebootUIPlugins,
|
||||
router: expect.any(Object),
|
||||
packageInfo: coreContext.env.packageInfo,
|
||||
serverBasePath: internalCorePreboot.http.basePath.serverBasePath,
|
||||
staticAssets: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -245,7 +245,23 @@ describe('CoreApp', () => {
|
|||
uiPlugins,
|
||||
router: expect.any(Object),
|
||||
packageInfo: coreContext.env.packageInfo,
|
||||
serverBasePath: internalCoreSetup.http.basePath.serverBasePath,
|
||||
staticAssets: expect.any(Object),
|
||||
});
|
||||
});
|
||||
|
||||
it('registers SHA-scoped and non-SHA-scoped UI bundle routes', async () => {
|
||||
const uiPlugins = emptyPlugins();
|
||||
internalCoreSetup.http.staticAssets.prependServerPath.mockReturnValue('/some-path');
|
||||
await coreApp.setup(internalCoreSetup, uiPlugins);
|
||||
|
||||
expect(internalCoreSetup.http.registerStaticDir).toHaveBeenCalledTimes(2);
|
||||
expect(internalCoreSetup.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/some-path',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(internalCoreSetup.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/ui/{path*}',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,6 +22,7 @@ import type {
|
|||
import type { UiPlugins } from '@kbn/core-plugins-base-server-internal';
|
||||
import type { HttpResources, HttpResourcesServiceToolkit } from '@kbn/core-http-resources-server';
|
||||
import type { InternalCorePreboot, InternalCoreSetup } from '@kbn/core-lifecycle-server-internal';
|
||||
import type { InternalStaticAssets } from '@kbn/core-http-server-internal';
|
||||
import { firstValueFrom, map, type Observable } from 'rxjs';
|
||||
import { CoreAppConfig, type CoreAppConfigType, CoreAppPath } from './core_app_config';
|
||||
import { registerBundleRoutes } from './bundle_routes';
|
||||
|
@ -33,6 +34,7 @@ interface CommonRoutesParams {
|
|||
httpResources: HttpResources;
|
||||
basePath: IBasePath;
|
||||
uiPlugins: UiPlugins;
|
||||
staticAssets: InternalStaticAssets;
|
||||
onResourceNotFound: (
|
||||
req: KibanaRequest,
|
||||
res: HttpResourcesServiceToolkit & KibanaResponseFactory
|
||||
|
@ -77,10 +79,11 @@ export class CoreAppsService {
|
|||
this.registerCommonDefaultRoutes({
|
||||
basePath: corePreboot.http.basePath,
|
||||
httpResources: corePreboot.httpResources.createRegistrar(router),
|
||||
staticAssets: corePreboot.http.staticAssets,
|
||||
router,
|
||||
uiPlugins,
|
||||
onResourceNotFound: async (req, res) =>
|
||||
// THe API consumers might call various Kibana APIs (e.g. `/api/status`) when Kibana is still at the preboot
|
||||
// The API consumers might call various Kibana APIs (e.g. `/api/status`) when Kibana is still at the preboot
|
||||
// stage, and the main HTTP server that registers API handlers isn't up yet. At this stage we don't know if
|
||||
// the API endpoint exists or not, and hence cannot reply with `404`. We also should not reply with completely
|
||||
// unexpected response (`200 text/html` for the Core app). The only suitable option is to reply with `503`
|
||||
|
@ -125,6 +128,7 @@ export class CoreAppsService {
|
|||
this.registerCommonDefaultRoutes({
|
||||
basePath: coreSetup.http.basePath,
|
||||
httpResources: resources,
|
||||
staticAssets: coreSetup.http.staticAssets,
|
||||
router,
|
||||
uiPlugins,
|
||||
onResourceNotFound: async (req, res) => res.notFound(),
|
||||
|
@ -210,6 +214,7 @@ export class CoreAppsService {
|
|||
private registerCommonDefaultRoutes({
|
||||
router,
|
||||
basePath,
|
||||
staticAssets,
|
||||
uiPlugins,
|
||||
onResourceNotFound,
|
||||
httpResources,
|
||||
|
@ -259,17 +264,23 @@ export class CoreAppsService {
|
|||
registerBundleRoutes({
|
||||
router,
|
||||
uiPlugins,
|
||||
staticAssets,
|
||||
packageInfo: this.env.packageInfo,
|
||||
serverBasePath: basePath.serverBasePath,
|
||||
});
|
||||
}
|
||||
|
||||
// After the package is built and bootstrap extracts files to bazel-bin,
|
||||
// assets are exposed at the root of the package and in the package's node_modules dir
|
||||
private registerStaticDirs(core: InternalCoreSetup | InternalCorePreboot) {
|
||||
core.http.registerStaticDir(
|
||||
'/ui/{path*}',
|
||||
fromRoot('node_modules/@kbn/core-apps-server-internal/assets')
|
||||
);
|
||||
/**
|
||||
* Serve UI from sha-scoped and not-sha-scoped paths to allow time for plugin code to migrate
|
||||
* Eventually we only want to serve from the sha scoped path
|
||||
*/
|
||||
[core.http.staticAssets.prependServerPath('/ui/{path*}'), '/ui/{path*}'].forEach((path) => {
|
||||
core.http.registerStaticDir(
|
||||
path,
|
||||
fromRoot('node_modules/@kbn/core-apps-server-internal/assets')
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"@kbn/core-lifecycle-server-mocks",
|
||||
"@kbn/core-ui-settings-server",
|
||||
"@kbn/monaco",
|
||||
"@kbn/core-http-server-internal",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -24,6 +24,7 @@ function createCoreContext({ production = false }: { production?: boolean } = {}
|
|||
branch: 'branch',
|
||||
buildNum: 100,
|
||||
buildSha: 'buildSha',
|
||||
buildShaShort: 'buildShaShort',
|
||||
dist: false,
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
buildFlavor: 'traditional',
|
||||
|
|
|
@ -17,6 +17,7 @@ export type {
|
|||
InternalHttpServiceStart,
|
||||
} from './src/types';
|
||||
export { BasePath } from './src/base_path_service';
|
||||
export { type InternalStaticAssets, StaticAssets } from './src/static_assets';
|
||||
|
||||
export { cspConfig, CspConfig, type CspConfigType } from './src/csp';
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { CdnConfig } from './cdn';
|
||||
import { CdnConfig } from './cdn_config';
|
||||
|
||||
describe('CdnConfig', () => {
|
||||
it.each([
|
|
@ -14,15 +14,15 @@ export interface Input {
|
|||
}
|
||||
|
||||
export class CdnConfig {
|
||||
private url: undefined | URL;
|
||||
private readonly url: undefined | URL;
|
||||
constructor(url?: string) {
|
||||
if (url) {
|
||||
this.url = new URL(url); // This will throw for invalid URLs
|
||||
this.url = new URL(url); // This will throw for invalid URLs, although should be validated before reaching this point
|
||||
}
|
||||
}
|
||||
|
||||
public get host(): undefined | string {
|
||||
return this.url?.host ?? undefined;
|
||||
return this.url?.host;
|
||||
}
|
||||
|
||||
public get baseHref(): undefined | string {
|
|
@ -16,8 +16,8 @@ const invalidHostnames = ['asdf$%^', '0'];
|
|||
|
||||
let mockHostname = 'kibana-hostname';
|
||||
|
||||
jest.mock('os', () => {
|
||||
const original = jest.requireActual('os');
|
||||
jest.mock('node:os', () => {
|
||||
const original = jest.requireActual('node:os');
|
||||
|
||||
return {
|
||||
...original,
|
||||
|
@ -530,6 +530,29 @@ describe('restrictInternalApis', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('cdn', () => {
|
||||
it('allows correct URL', () => {
|
||||
expect(config.schema.validate({ cdn: { url: 'https://cdn.example.com' } })).toMatchObject({
|
||||
cdn: { url: 'https://cdn.example.com' },
|
||||
});
|
||||
});
|
||||
it.each([['foo'], ['http:./']])('throws for invalid URL %s', (url) => {
|
||||
expect(() => config.schema.validate({ cdn: { url } })).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[cdn.url]: expected URI with scheme [http|https]."`
|
||||
);
|
||||
});
|
||||
it.each([
|
||||
['https://cdn.example.com:1234/asd?thing=1', 'URL query string not allowed'],
|
||||
['https://cdn.example.com:1234/asd#cool', 'URL fragment not allowed'],
|
||||
[
|
||||
'https://cdn.example.com:1234/asd?thing=1#cool',
|
||||
'URL fragment not allowed, but found "#cool"\nURL query string not allowed, but found "?thing=1"',
|
||||
],
|
||||
])('throws for disallowed values %s', (url, expecterError) => {
|
||||
expect(() => config.schema.validate({ cdn: { url } })).toThrow(expecterError);
|
||||
});
|
||||
});
|
||||
|
||||
describe('HttpConfig', () => {
|
||||
it('converts customResponseHeaders to strings or arrays of strings', () => {
|
||||
const httpSchema = config.schema;
|
||||
|
|
|
@ -12,8 +12,8 @@ import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal';
|
|||
import { uuidRegexp } from '@kbn/core-base-server-internal';
|
||||
import type { ICspConfig, IExternalUrlConfig } from '@kbn/core-http-server';
|
||||
|
||||
import { hostname } from 'os';
|
||||
import url from 'url';
|
||||
import { hostname, EOL } from 'node:os';
|
||||
import url, { URL } from 'node:url';
|
||||
|
||||
import type { Duration } from 'moment';
|
||||
import type { IHttpEluMonitorConfig } from '@kbn/core-http-server/src/elu_monitor';
|
||||
|
@ -24,7 +24,7 @@ import {
|
|||
securityResponseHeadersSchema,
|
||||
parseRawSecurityResponseHeadersConfig,
|
||||
} from './security_response_headers_config';
|
||||
import { CdnConfig } from './cdn';
|
||||
import { CdnConfig } from './cdn_config';
|
||||
|
||||
const validBasePathRegex = /^\/.*[^\/]$/;
|
||||
|
||||
|
@ -40,6 +40,24 @@ const validHostName = () => {
|
|||
return hostname().replace(/[^\x00-\x7F]/g, '');
|
||||
};
|
||||
|
||||
/**
|
||||
* We assume the URL does not contain anything after the pathname so that
|
||||
* we can safely append values to the pathname at runtime.
|
||||
*/
|
||||
function validateCdnURL(urlString: string): undefined | string {
|
||||
const cdnURL = new URL(urlString);
|
||||
const errors: string[] = [];
|
||||
if (cdnURL.hash.length) {
|
||||
errors.push(`URL fragment not allowed, but found "${cdnURL.hash}"`);
|
||||
}
|
||||
if (cdnURL.search.length) {
|
||||
errors.push(`URL query string not allowed, but found "${cdnURL.search}"`);
|
||||
}
|
||||
if (errors.length) {
|
||||
return `CDN URL "${cdnURL.href}" is invalid:${EOL}${errors.join(EOL)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const configSchema = schema.object(
|
||||
{
|
||||
name: schema.string({ defaultValue: () => validHostName() }),
|
||||
|
@ -60,7 +78,7 @@ const configSchema = schema.object(
|
|||
},
|
||||
}),
|
||||
cdn: schema.object({
|
||||
url: schema.maybe(schema.uri({ scheme: ['http', 'https'] })),
|
||||
url: schema.maybe(schema.uri({ scheme: ['http', 'https'], validate: validateCdnURL })),
|
||||
}),
|
||||
cors: schema.object(
|
||||
{
|
||||
|
|
|
@ -30,6 +30,7 @@ import { Readable } from 'stream';
|
|||
import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
|
||||
import moment from 'moment';
|
||||
import { of, Observable, BehaviorSubject } from 'rxjs';
|
||||
import { mockCoreContext } from '@kbn/core-base-server-mocks';
|
||||
|
||||
const routerOptions: RouterOptions = {
|
||||
isDev: false,
|
||||
|
@ -54,8 +55,9 @@ let config$: Observable<HttpConfig>;
|
|||
let configWithSSL: HttpConfig;
|
||||
let configWithSSL$: Observable<HttpConfig>;
|
||||
|
||||
const loggingService = loggingSystemMock.create();
|
||||
const logger = loggingService.get();
|
||||
const coreContext = mockCoreContext.create();
|
||||
const loggingService = coreContext.logger;
|
||||
const logger = coreContext.logger.get();
|
||||
const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
|
||||
|
||||
let certificate: string;
|
||||
|
@ -99,7 +101,7 @@ beforeEach(() => {
|
|||
} as HttpConfig;
|
||||
configWithSSL$ = of(configWithSSL);
|
||||
|
||||
server = new HttpServer(loggingService, 'tests', of(config.shutdownTimeout));
|
||||
server = new HttpServer(coreContext, 'tests', of(config.shutdownTimeout));
|
||||
});
|
||||
|
||||
afterEach(async () => {
|
||||
|
|
|
@ -48,6 +48,8 @@ import { performance } from 'perf_hooks';
|
|||
import { isBoom } from '@hapi/boom';
|
||||
import { identity } from 'lodash';
|
||||
import { IHttpEluMonitorConfig } from '@kbn/core-http-server/src/elu_monitor';
|
||||
import { Env } from '@kbn/config';
|
||||
import { CoreContext } from '@kbn/core-base-server-internal';
|
||||
import { HttpConfig } from './http_config';
|
||||
import { adoptToHapiAuthFormat } from './lifecycle/auth';
|
||||
import { adoptToHapiOnPreAuth } from './lifecycle/on_pre_auth';
|
||||
|
@ -178,15 +180,20 @@ export class HttpServer {
|
|||
private stopped = false;
|
||||
|
||||
private readonly log: Logger;
|
||||
private readonly logger: LoggerFactory;
|
||||
private readonly authState: AuthStateStorage;
|
||||
private readonly authRequestHeaders: AuthHeadersStorage;
|
||||
private readonly authResponseHeaders: AuthHeadersStorage;
|
||||
private readonly env: Env;
|
||||
|
||||
constructor(
|
||||
private readonly logger: LoggerFactory,
|
||||
private readonly coreContext: CoreContext,
|
||||
private readonly name: string,
|
||||
private readonly shutdownTimeout$: Observable<Duration>
|
||||
) {
|
||||
const { logger, env } = this.coreContext;
|
||||
this.logger = logger;
|
||||
this.env = env;
|
||||
this.authState = new AuthStateStorage(() => this.authRegistered);
|
||||
this.authRequestHeaders = new AuthHeadersStorage();
|
||||
this.authResponseHeaders = new AuthHeadersStorage();
|
||||
|
@ -269,7 +276,11 @@ export class HttpServer {
|
|||
this.setupResponseLogging();
|
||||
this.setupGracefulShutdownHandlers();
|
||||
|
||||
const staticAssets = new StaticAssets(basePathService, config.cdn);
|
||||
const staticAssets = new StaticAssets({
|
||||
basePath: basePathService,
|
||||
cdnConfig: config.cdn,
|
||||
shaDigest: this.env.packageInfo.buildShaShort,
|
||||
});
|
||||
|
||||
return {
|
||||
registerRouter: this.registerRouter.bind(this),
|
||||
|
|
|
@ -76,8 +76,8 @@ export class HttpService
|
|||
configService.atPath<ExternalUrlConfigType>(externalUrlConfig.path),
|
||||
]).pipe(map(([http, csp, externalUrl]) => new HttpConfig(http, csp, externalUrl)));
|
||||
const shutdownTimeout$ = this.config$.pipe(map(({ shutdownTimeout }) => shutdownTimeout));
|
||||
this.prebootServer = new HttpServer(logger, 'Preboot', shutdownTimeout$);
|
||||
this.httpServer = new HttpServer(logger, 'Kibana', shutdownTimeout$);
|
||||
this.prebootServer = new HttpServer(coreContext, 'Preboot', shutdownTimeout$);
|
||||
this.httpServer = new HttpServer(coreContext, 'Kibana', shutdownTimeout$);
|
||||
this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server'));
|
||||
}
|
||||
|
||||
|
|
|
@ -1,61 +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 { StaticAssets } from './static_assets';
|
||||
import { BasePath } from './base_path_service';
|
||||
import { CdnConfig } from './cdn';
|
||||
|
||||
describe('StaticAssets', () => {
|
||||
let basePath: BasePath;
|
||||
let cdnConfig: CdnConfig;
|
||||
let staticAssets: StaticAssets;
|
||||
|
||||
beforeEach(() => {
|
||||
basePath = new BasePath('/base-path');
|
||||
});
|
||||
|
||||
describe('#getHrefBase()', () => {
|
||||
it('provides fallback to server base path', () => {
|
||||
cdnConfig = CdnConfig.from();
|
||||
staticAssets = new StaticAssets(basePath, cdnConfig);
|
||||
expect(staticAssets.getHrefBase()).toEqual('/base-path');
|
||||
});
|
||||
|
||||
it('provides the correct HREF given a CDN is configured', () => {
|
||||
cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' });
|
||||
staticAssets = new StaticAssets(basePath, cdnConfig);
|
||||
expect(staticAssets.getHrefBase()).toEqual('https://cdn.example.com/test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getPluginAssetHref()', () => {
|
||||
it('returns the expected value when CDN config is not set', () => {
|
||||
cdnConfig = CdnConfig.from();
|
||||
staticAssets = new StaticAssets(basePath, cdnConfig);
|
||||
expect(staticAssets.getPluginAssetHref('foo', 'path/to/img.gif')).toEqual(
|
||||
'/base-path/plugins/foo/assets/path/to/img.gif'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the expected value when CDN config is set', () => {
|
||||
cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' });
|
||||
staticAssets = new StaticAssets(basePath, cdnConfig);
|
||||
expect(staticAssets.getPluginAssetHref('bar', 'path/to/img.gif')).toEqual(
|
||||
'https://cdn.example.com/test/plugins/bar/assets/path/to/img.gif'
|
||||
);
|
||||
});
|
||||
|
||||
it('removes leading slash from the', () => {
|
||||
cdnConfig = CdnConfig.from();
|
||||
staticAssets = new StaticAssets(basePath, cdnConfig);
|
||||
expect(staticAssets.getPluginAssetHref('dolly', '/path/for/something.svg')).toEqual(
|
||||
'/base-path/plugins/dolly/assets/path/for/something.svg'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,39 +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 { BasePath } from './base_path_service';
|
||||
import { CdnConfig } from './cdn';
|
||||
|
||||
export interface InternalStaticAssets {
|
||||
getHrefBase(): string;
|
||||
getPluginAssetHref(pluginName: string, assetPath: string): string;
|
||||
}
|
||||
|
||||
export class StaticAssets implements InternalStaticAssets {
|
||||
private readonly assetsHrefBase: string;
|
||||
|
||||
constructor(basePath: BasePath, cdnConfig: CdnConfig) {
|
||||
const hrefToUse = cdnConfig.baseHref ?? basePath.serverBasePath;
|
||||
this.assetsHrefBase = hrefToUse.endsWith('/') ? hrefToUse.slice(0, -1) : hrefToUse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a href (hypertext reference) intended to be used as the base for constructing
|
||||
* other hrefs to static assets.
|
||||
*/
|
||||
getHrefBase(): string {
|
||||
return this.assetsHrefBase;
|
||||
}
|
||||
|
||||
getPluginAssetHref(pluginName: string, assetPath: string): string {
|
||||
if (assetPath.startsWith('/')) {
|
||||
assetPath = assetPath.slice(1);
|
||||
}
|
||||
return `${this.assetsHrefBase}/plugins/${pluginName}/assets/${assetPath}`;
|
||||
}
|
||||
}
|
|
@ -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 { type InternalStaticAssets, StaticAssets } from './static_assets';
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* 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 { StaticAssets, type StaticAssetsParams } from './static_assets';
|
||||
import { BasePath } from '../base_path_service';
|
||||
import { CdnConfig } from '../cdn_config';
|
||||
|
||||
describe('StaticAssets', () => {
|
||||
let basePath: BasePath;
|
||||
let cdnConfig: CdnConfig;
|
||||
let staticAssets: StaticAssets;
|
||||
let args: StaticAssetsParams;
|
||||
|
||||
beforeEach(() => {
|
||||
basePath = new BasePath('/base-path');
|
||||
cdnConfig = CdnConfig.from();
|
||||
args = { basePath, cdnConfig, shaDigest: '' };
|
||||
});
|
||||
|
||||
describe('#getHrefBase()', () => {
|
||||
it('provides fallback to server base path', () => {
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getHrefBase()).toEqual('/base-path');
|
||||
});
|
||||
|
||||
it('provides the correct HREF given a CDN is configured', () => {
|
||||
args.cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' });
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getHrefBase()).toEqual('https://cdn.example.com/test');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getPluginAssetHref()', () => {
|
||||
it('returns the expected value when CDN is not configured', () => {
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getPluginAssetHref('foo', 'path/to/img.gif')).toEqual(
|
||||
'/base-path/plugins/foo/assets/path/to/img.gif'
|
||||
);
|
||||
});
|
||||
|
||||
it('returns the expected value when CDN is configured', () => {
|
||||
args.cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' });
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getPluginAssetHref('bar', 'path/to/img.gif')).toEqual(
|
||||
'https://cdn.example.com/test/plugins/bar/assets/path/to/img.gif'
|
||||
);
|
||||
});
|
||||
|
||||
it('removes leading and trailing slash from the assetPath', () => {
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getPluginAssetHref('dolly', '/path/for/something.svg/')).toEqual(
|
||||
'/base-path/plugins/dolly/assets/path/for/something.svg'
|
||||
);
|
||||
});
|
||||
it('removes leading and trailing slash from the assetPath when CDN is configured', () => {
|
||||
args.cdnConfig = CdnConfig.from({ url: 'https://cdn.example.com/test' });
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getPluginAssetHref('dolly', '/path/for/something.svg/')).toEqual(
|
||||
'https://cdn.example.com/test/plugins/dolly/assets/path/for/something.svg'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a SHA digest provided', () => {
|
||||
describe('cdn', () => {
|
||||
it.each([
|
||||
['https://cdn.example.com', 'https://cdn.example.com/beef', undefined],
|
||||
['https://cdn.example.com:1234', 'https://cdn.example.com:1234/beef', undefined],
|
||||
[
|
||||
'https://cdn.example.com:1234/roast',
|
||||
'https://cdn.example.com:1234/roast/beef',
|
||||
undefined,
|
||||
],
|
||||
// put slashes around shaDigest
|
||||
[
|
||||
'https://cdn.example.com:1234/roast-slash',
|
||||
'https://cdn.example.com:1234/roast-slash/beef',
|
||||
'/beef/',
|
||||
],
|
||||
])('suffixes the digest to the CDNs path value (%s)', (url, expectedHref, shaDigest) => {
|
||||
args.shaDigest = shaDigest ?? 'beef';
|
||||
args.cdnConfig = CdnConfig.from({ url });
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getHrefBase()).toEqual(expectedHref);
|
||||
});
|
||||
});
|
||||
|
||||
describe('base path', () => {
|
||||
it.each([
|
||||
['', '/beef', undefined],
|
||||
['/', '/beef', undefined],
|
||||
['/roast', '/roast/beef', undefined],
|
||||
['/roast/', '/roast/beef', '/beef/'], // cheeky test adding a slashes to digest
|
||||
])('suffixes the digest to the server base path "%s")', (url, expectedPath, shaDigest) => {
|
||||
basePath = new BasePath(url);
|
||||
args.basePath = basePath;
|
||||
args.shaDigest = shaDigest ?? 'beef';
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getHrefBase()).toEqual(expectedPath);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getPluginServerPath()', () => {
|
||||
it('provides the path plugin assets can use for server routes', () => {
|
||||
args.shaDigest = '1234';
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.getPluginServerPath('myPlugin', '/fun/times')).toEqual(
|
||||
'/1234/plugins/myPlugin/assets/fun/times'
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('#prependPublicUrl()', () => {
|
||||
it('with a CDN it appends as expected', () => {
|
||||
args.cdnConfig = CdnConfig.from({ url: 'http://cdn.example.com/cool?123=true' });
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.prependPublicUrl('beans')).toEqual(
|
||||
'http://cdn.example.com/cool/beans?123=true'
|
||||
);
|
||||
});
|
||||
|
||||
it('without a CDN it appends as expected', () => {
|
||||
staticAssets = new StaticAssets(args);
|
||||
expect(staticAssets.prependPublicUrl('/cool/beans')).toEqual('/base-path/cool/beans');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
* 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 { BasePath } from '../base_path_service';
|
||||
import { CdnConfig } from '../cdn_config';
|
||||
import {
|
||||
suffixPathnameToPathname,
|
||||
suffixPathnameToURLPathname,
|
||||
removeSurroundingSlashes,
|
||||
} from './util';
|
||||
|
||||
export interface InternalStaticAssets {
|
||||
getHrefBase(): string;
|
||||
/**
|
||||
* Intended for use by server code rendering UI or generating links to static assets
|
||||
* that will ultimately be called from the browser and must respect settings like
|
||||
* serverBasePath
|
||||
*/
|
||||
getPluginAssetHref(pluginName: string, assetPath: string): string;
|
||||
/**
|
||||
* Intended for use by server code wanting to register static assets against Kibana
|
||||
* as server paths
|
||||
*/
|
||||
getPluginServerPath(pluginName: string, assetPath: string): string;
|
||||
/**
|
||||
* Similar to getPluginServerPath, but not plugin-scoped
|
||||
*/
|
||||
prependServerPath(pathname: string): string;
|
||||
|
||||
/**
|
||||
* Will append the given path segment to the configured public path.
|
||||
*
|
||||
* @note This could return a path or full URL depending on whether a CDN is configured.
|
||||
*/
|
||||
prependPublicUrl(pathname: string): string;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export interface StaticAssetsParams {
|
||||
basePath: BasePath;
|
||||
cdnConfig: CdnConfig;
|
||||
shaDigest: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convention is for trailing slashes in pathnames are stripped.
|
||||
*/
|
||||
export class StaticAssets implements InternalStaticAssets {
|
||||
private readonly assetsHrefBase: string;
|
||||
private readonly assetsServerPathBase: string;
|
||||
private readonly hasCdnHost: boolean;
|
||||
|
||||
constructor({ basePath, cdnConfig, shaDigest }: StaticAssetsParams) {
|
||||
const cdnBaseHref = cdnConfig.baseHref;
|
||||
if (cdnBaseHref) {
|
||||
this.hasCdnHost = true;
|
||||
this.assetsHrefBase = suffixPathnameToURLPathname(cdnBaseHref, shaDigest);
|
||||
} else {
|
||||
this.hasCdnHost = false;
|
||||
this.assetsHrefBase = suffixPathnameToPathname(basePath.serverBasePath, shaDigest);
|
||||
}
|
||||
this.assetsServerPathBase = `/${shaDigest}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a href (hypertext reference) intended to be used as the base for constructing
|
||||
* other hrefs to static assets.
|
||||
*/
|
||||
public getHrefBase(): string {
|
||||
return this.assetsHrefBase;
|
||||
}
|
||||
|
||||
public getPluginAssetHref(pluginName: string, assetPath: string): string {
|
||||
if (assetPath.startsWith('/')) {
|
||||
assetPath = assetPath.slice(1);
|
||||
}
|
||||
return `${this.assetsHrefBase}/plugins/${pluginName}/assets/${removeSurroundingSlashes(
|
||||
assetPath
|
||||
)}`;
|
||||
}
|
||||
|
||||
public prependServerPath(path: string): string {
|
||||
return `${this.assetsServerPathBase}/${removeSurroundingSlashes(path)}`;
|
||||
}
|
||||
|
||||
public prependPublicUrl(pathname: string): string {
|
||||
if (this.hasCdnHost) {
|
||||
return suffixPathnameToURLPathname(this.assetsHrefBase, pathname);
|
||||
}
|
||||
return suffixPathnameToPathname(this.assetsHrefBase, pathname);
|
||||
}
|
||||
|
||||
public getPluginServerPath(pluginName: string, assetPath: string): string {
|
||||
return `${this.assetsServerPathBase}/plugins/${pluginName}/assets/${removeSurroundingSlashes(
|
||||
assetPath
|
||||
)}`;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { URL, format } from 'node:url';
|
||||
|
||||
function isEmptyPathname(pathname: string): boolean {
|
||||
return !pathname || pathname === '/';
|
||||
}
|
||||
|
||||
function removeTailSlashes(pathname: string): string {
|
||||
return pathname.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function removeLeadSlashes(pathname: string): string {
|
||||
return pathname.replace(/^\/+/, '');
|
||||
}
|
||||
|
||||
export function removeSurroundingSlashes(pathname: string): string {
|
||||
return removeLeadSlashes(removeTailSlashes(pathname));
|
||||
}
|
||||
|
||||
export function suffixPathnameToURLPathname(urlString: string, pathname: string): string {
|
||||
const url = new URL(urlString);
|
||||
url.pathname = suffixPathnameToPathname(url.pathname, pathname);
|
||||
return format(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a value to pathname. Pathname is assumed to come from URL.pathname
|
||||
* Also do some quality control on the path to ensure that it matches URL.pathname.
|
||||
*/
|
||||
export function suffixPathnameToPathname(pathnameA: string, pathnameB: string): string {
|
||||
if (isEmptyPathname(pathnameA)) {
|
||||
return `/${removeSurroundingSlashes(pathnameB)}`;
|
||||
}
|
||||
if (isEmptyPathname(pathnameB)) {
|
||||
return `/${removeSurroundingSlashes(pathnameA)}`;
|
||||
}
|
||||
return `/${removeSurroundingSlashes(pathnameA)}/${removeSurroundingSlashes(pathnameB)}`;
|
||||
}
|
|
@ -33,6 +33,7 @@
|
|||
"@kbn/core-execution-context-server-mocks",
|
||||
"@kbn/core-http-context-server-mocks",
|
||||
"@kbn/logging-mocks",
|
||||
"@kbn/core-base-server-mocks",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -87,8 +87,11 @@ const createInternalStaticAssetsMock = (
|
|||
basePath: BasePathMocked,
|
||||
cdnUrl: undefined | string = undefined
|
||||
): InternalStaticAssetsMocked => ({
|
||||
getHrefBase: jest.fn(() => cdnUrl ?? basePath.serverBasePath),
|
||||
getHrefBase: jest.fn().mockReturnValue(cdnUrl ?? basePath.serverBasePath),
|
||||
getPluginAssetHref: jest.fn().mockReturnValue(cdnUrl ?? basePath.serverBasePath),
|
||||
getPluginServerPath: jest.fn((v, _) => v),
|
||||
prependServerPath: jest.fn((v) => v),
|
||||
prependPublicUrl: jest.fn((v) => v),
|
||||
});
|
||||
|
||||
const createAuthMock = () => {
|
||||
|
@ -212,6 +215,7 @@ const createSetupContractMock = <
|
|||
getServerInfo: internalMock.getServerInfo,
|
||||
staticAssets: {
|
||||
getPluginAssetHref: jest.fn().mockImplementation((assetPath: string) => assetPath),
|
||||
prependPublicUrl: jest.fn().mockImplementation((pathname: string) => pathname),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -227,6 +231,7 @@ const createStartContractMock = () => {
|
|||
getServerInfo: jest.fn(),
|
||||
staticAssets: {
|
||||
getPluginAssetHref: jest.fn().mockImplementation((assetPath: string) => assetPath),
|
||||
prependPublicUrl: jest.fn().mockImplementation((pathname: string) => pathname),
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -23,4 +23,23 @@ export interface IStaticAssets {
|
|||
* ```
|
||||
*/
|
||||
getPluginAssetHref(assetPath: string): string;
|
||||
|
||||
/**
|
||||
* Will return an href, either a path for or full URL with the provided path
|
||||
* appended to the static assets public base path.
|
||||
*
|
||||
* Useful for instances were you need to render your own HTML page and link to
|
||||
* certain static assets.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // I want to retrieve the href for Kibana's favicon, requires knowledge of path:
|
||||
* const favIconHref = core.http.statisAssets.prependPublicUrl('/ui/favicons/favicon.svg');
|
||||
* ```
|
||||
*
|
||||
* @note Only use this if you know what you are doing and there is no other option.
|
||||
* This creates a strong coupling between asset dir structure and your code.
|
||||
* @param pathname
|
||||
*/
|
||||
prependPublicUrl(pathname: string): string;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ export const createPluginInitializerContextMock = (config: unknown = {}) => {
|
|||
branch: 'branch',
|
||||
buildNum: 100,
|
||||
buildSha: 'buildSha',
|
||||
buildShaShort: 'buildShaShort',
|
||||
dist: false,
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
buildFlavor: 'traditional',
|
||||
|
|
|
@ -49,6 +49,7 @@ const createPluginInitializerContextMock = (
|
|||
branch: 'branch',
|
||||
buildNum: 100,
|
||||
buildSha: 'buildSha',
|
||||
buildShaShort: 'buildShaShort',
|
||||
dist: false,
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
buildFlavor,
|
||||
|
|
|
@ -19,6 +19,7 @@ const packageInfo: PackageInfo = {
|
|||
branch: 'master',
|
||||
buildNum: 1,
|
||||
buildSha: '',
|
||||
buildShaShort: '',
|
||||
version: '7.0.0-alpha1',
|
||||
dist: false,
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
|
|
|
@ -236,6 +236,7 @@ export function createPluginSetupContext<TPlugin, TPluginDependencies>({
|
|||
registerOnPreResponse: deps.http.registerOnPreResponse,
|
||||
basePath: deps.http.basePath,
|
||||
staticAssets: {
|
||||
prependPublicUrl: (pathname: string) => deps.http.staticAssets.prependPublicUrl(pathname),
|
||||
getPluginAssetHref: (assetPath: string) =>
|
||||
deps.http.staticAssets.getPluginAssetHref(plugin.name, assetPath),
|
||||
},
|
||||
|
@ -329,6 +330,7 @@ export function createPluginStartContext<TPlugin, TPluginDependencies>({
|
|||
basePath: deps.http.basePath,
|
||||
getServerInfo: deps.http.getServerInfo,
|
||||
staticAssets: {
|
||||
prependPublicUrl: (pathname: string) => deps.http.staticAssets.prependPublicUrl(pathname),
|
||||
getPluginAssetHref: (assetPath: string) =>
|
||||
deps.http.staticAssets.getPluginAssetHref(plugin.name, assetPath),
|
||||
},
|
||||
|
|
|
@ -1165,8 +1165,10 @@ describe('PluginsService', () => {
|
|||
});
|
||||
|
||||
describe('plugin initialization', () => {
|
||||
let prebootPlugins: PluginWrapper[];
|
||||
let standardPlugins: PluginWrapper[];
|
||||
beforeEach(() => {
|
||||
const prebootPlugins = [
|
||||
prebootPlugins = [
|
||||
createPlugin('plugin-1-preboot', {
|
||||
type: PluginType.preboot,
|
||||
path: 'path-1-preboot',
|
||||
|
@ -1178,7 +1180,7 @@ describe('PluginsService', () => {
|
|||
version: 'version-2',
|
||||
}),
|
||||
];
|
||||
const standardPlugins = [
|
||||
standardPlugins = [
|
||||
createPlugin('plugin-1-standard', {
|
||||
path: 'path-1-standard',
|
||||
version: 'version-1',
|
||||
|
@ -1299,6 +1301,31 @@ describe('PluginsService', () => {
|
|||
expect(standardMockPluginSystem.setupPlugins).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('#preboot registers expected static dirs', async () => {
|
||||
prebootDeps.http.staticAssets.getPluginServerPath.mockImplementation(
|
||||
(pluginName: string) => `/static-assets/${pluginName}`
|
||||
);
|
||||
await pluginsService.discover({ environment: environmentPreboot, node: nodePreboot });
|
||||
await pluginsService.preboot(prebootDeps);
|
||||
expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledTimes(prebootPlugins.length * 2);
|
||||
expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/static-assets/plugin-1-preboot',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/plugins/plugin-1-preboot/assets/{path*}',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/static-assets/plugin-2-preboot',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(prebootDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/plugins/plugin-2-preboot/assets/{path*}',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
|
||||
it('#setup does initialize `standard` plugins if plugins.initialize is true', async () => {
|
||||
config$.next({ plugins: { initialize: true } });
|
||||
await pluginsService.discover({ environment: environmentPreboot, node: nodePreboot });
|
||||
|
@ -1319,6 +1346,32 @@ describe('PluginsService', () => {
|
|||
expect(prebootMockPluginSystem.setupPlugins).not.toHaveBeenCalled();
|
||||
expect(initialized).toBe(false);
|
||||
});
|
||||
|
||||
it('#setup registers expected static dirs', async () => {
|
||||
await pluginsService.discover({ environment: environmentPreboot, node: nodePreboot });
|
||||
await pluginsService.preboot(prebootDeps);
|
||||
setupDeps.http.staticAssets.getPluginServerPath.mockImplementation(
|
||||
(pluginName: string) => `/static-assets/${pluginName}`
|
||||
);
|
||||
await pluginsService.setup(setupDeps);
|
||||
expect(setupDeps.http.registerStaticDir).toHaveBeenCalledTimes(standardPlugins.length * 2);
|
||||
expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/static-assets/plugin-1-standard',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/plugins/plugin-1-standard/assets/{path*}',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/static-assets/plugin-2-standard',
|
||||
expect.any(String)
|
||||
);
|
||||
expect(setupDeps.http.registerStaticDir).toHaveBeenCalledWith(
|
||||
'/plugins/plugin-2-standard/assets/{path*}',
|
||||
expect.any(String)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getExposedPluginConfigsToUsage', () => {
|
||||
|
|
|
@ -448,10 +448,16 @@ export class PluginsService
|
|||
uiPluginInternalInfo: Map<PluginName, InternalPluginInfo>
|
||||
) {
|
||||
for (const [pluginName, pluginInfo] of uiPluginInternalInfo) {
|
||||
deps.http.registerStaticDir(
|
||||
/**
|
||||
* Serve UI from sha-scoped and not-sha-scoped paths to allow time for plugin code to migrate
|
||||
* Eventually we only want to serve from the sha scoped path
|
||||
*/
|
||||
[
|
||||
deps.http.staticAssets.getPluginServerPath(pluginName, '{path*}'),
|
||||
`/plugins/${pluginName}/assets/{path*}`,
|
||||
pluginInfo.publicAssetsDir
|
||||
);
|
||||
].forEach((path) => {
|
||||
deps.http.registerStaticDir(path, pluginInfo.publicAssetsDir);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -92,6 +93,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -156,6 +158,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -224,6 +227,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -288,6 +292,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -352,6 +357,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -420,6 +426,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -484,6 +491,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -553,6 +561,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -621,6 +630,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -690,6 +700,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -763,6 +774,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -827,6 +839,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -896,6 +909,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -969,6 +983,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
@ -1038,6 +1053,7 @@ Object {
|
|||
"buildFlavor": Any<String>,
|
||||
"buildNum": Any<Number>,
|
||||
"buildSha": Any<String>,
|
||||
"buildShaShort": "XXXXXX",
|
||||
"dist": Any<Boolean>,
|
||||
"version": Any<String>,
|
||||
},
|
||||
|
|
|
@ -25,6 +25,7 @@ const createPackageInfo = (parts: Partial<PackageInfo> = {}): PackageInfo => ({
|
|||
branch: 'master',
|
||||
buildNum: 42,
|
||||
buildSha: 'buildSha',
|
||||
buildShaShort: 'buildShaShort',
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
dist: false,
|
||||
version: '8.0.0',
|
||||
|
@ -62,7 +63,7 @@ describe('bootstrapRenderer', () => {
|
|||
auth,
|
||||
packageInfo,
|
||||
uiPlugins,
|
||||
baseHref: '/base-path',
|
||||
baseHref: `/base-path/${packageInfo.buildShaShort}`, // the base href as provided by static assets module
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -319,7 +320,7 @@ describe('bootstrapRenderer', () => {
|
|||
expect(getPluginsBundlePathsMock).toHaveBeenCalledWith({
|
||||
isAnonymousPage,
|
||||
uiPlugins,
|
||||
bundlesHref: '/base-path/42/bundles',
|
||||
bundlesHref: '/base-path/buildShaShort/bundles',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -338,7 +339,7 @@ describe('bootstrapRenderer', () => {
|
|||
|
||||
expect(getJsDependencyPathsMock).toHaveBeenCalledTimes(1);
|
||||
expect(getJsDependencyPathsMock).toHaveBeenCalledWith(
|
||||
'/base-path/42/bundles',
|
||||
'/base-path/buildShaShort/bundles',
|
||||
pluginsBundlePaths
|
||||
);
|
||||
});
|
||||
|
|
|
@ -79,8 +79,7 @@ export const bootstrapRendererFactory: BootstrapRendererFactory = ({
|
|||
themeVersion,
|
||||
darkMode,
|
||||
});
|
||||
const buildHash = packageInfo.buildNum;
|
||||
const bundlesHref = getBundlesHref(baseHref, String(buildHash));
|
||||
const bundlesHref = getBundlesHref(baseHref);
|
||||
|
||||
const bundlePaths = getPluginsBundlePaths({
|
||||
uiPlugins,
|
||||
|
|
|
@ -16,14 +16,14 @@ describe('getStylesheetPaths', () => {
|
|||
getStylesheetPaths({
|
||||
darkMode: true,
|
||||
themeVersion: 'v8',
|
||||
baseHref: '/base-path',
|
||||
baseHref: '/base-path/buildShaShort',
|
||||
buildNum: 17,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/base-path/17/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.dark.css",
|
||||
"/base-path/17/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css",
|
||||
"/base-path/ui/legacy_dark_theme.min.css",
|
||||
"/base-path/buildShaShort/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.dark.css",
|
||||
"/base-path/buildShaShort/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css",
|
||||
"/base-path/buildShaShort/ui/legacy_dark_theme.min.css",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
@ -36,14 +36,14 @@ describe('getStylesheetPaths', () => {
|
|||
getStylesheetPaths({
|
||||
darkMode: false,
|
||||
themeVersion: 'v8',
|
||||
baseHref: '/base-path',
|
||||
baseHref: '/base-path/buildShaShort',
|
||||
buildNum: 69,
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"/base-path/69/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css",
|
||||
"/base-path/69/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css",
|
||||
"/base-path/ui/legacy_light_theme.min.css",
|
||||
"/base-path/buildShaShort/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css",
|
||||
"/base-path/buildShaShort/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css",
|
||||
"/base-path/buildShaShort/ui/legacy_light_theme.min.css",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -22,8 +22,7 @@ export const getSettingValue = <T>(
|
|||
return convert(value);
|
||||
};
|
||||
|
||||
export const getBundlesHref = (baseHref: string, buildNr: string): string =>
|
||||
`${baseHref}/${buildNr}/bundles`;
|
||||
export const getBundlesHref = (baseHref: string): string => `${baseHref}/bundles`;
|
||||
|
||||
export const getStylesheetPaths = ({
|
||||
themeVersion,
|
||||
|
@ -36,7 +35,7 @@ export const getStylesheetPaths = ({
|
|||
buildNum: number;
|
||||
baseHref: string;
|
||||
}) => {
|
||||
const bundlesHref = getBundlesHref(baseHref, String(buildNum));
|
||||
const bundlesHref = getBundlesHref(baseHref);
|
||||
return [
|
||||
...(darkMode
|
||||
? [
|
||||
|
|
|
@ -32,6 +32,7 @@ Env {
|
|||
"buildFlavor": "traditional",
|
||||
"buildNum": 9007199254740991,
|
||||
"buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"buildShaShort": "XXXXXXXXXXXX",
|
||||
"dist": false,
|
||||
"version": "v1",
|
||||
},
|
||||
|
@ -75,6 +76,7 @@ Env {
|
|||
"buildFlavor": "traditional",
|
||||
"buildNum": 9007199254740991,
|
||||
"buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"buildShaShort": "XXXXXXXXXXXX",
|
||||
"dist": false,
|
||||
"version": "v1",
|
||||
},
|
||||
|
@ -117,6 +119,7 @@ Env {
|
|||
"buildFlavor": "traditional",
|
||||
"buildNum": 9007199254740991,
|
||||
"buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"buildShaShort": "XXXXXXXXXXXX",
|
||||
"dist": false,
|
||||
"version": "some-version",
|
||||
},
|
||||
|
@ -159,6 +162,7 @@ Env {
|
|||
"buildFlavor": "traditional",
|
||||
"buildNum": 100,
|
||||
"buildSha": "feature-v1-build-sha",
|
||||
"buildShaShort": "feature-v1-b",
|
||||
"dist": true,
|
||||
"version": "v1",
|
||||
},
|
||||
|
@ -201,6 +205,7 @@ Env {
|
|||
"buildFlavor": "traditional",
|
||||
"buildNum": 9007199254740991,
|
||||
"buildSha": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"buildShaShort": "XXXXXXXXXXXX",
|
||||
"dist": false,
|
||||
"version": "v1",
|
||||
},
|
||||
|
@ -243,6 +248,7 @@ Env {
|
|||
"buildFlavor": "traditional",
|
||||
"buildNum": 100,
|
||||
"buildSha": "feature-v1-build-sha",
|
||||
"buildShaShort": "feature-v1-b",
|
||||
"dist": true,
|
||||
"version": "v1",
|
||||
},
|
||||
|
|
|
@ -248,3 +248,35 @@ describe('packageInfo.buildFlavor', () => {
|
|||
expect(env.packageInfo.buildFlavor).toEqual('traditional');
|
||||
});
|
||||
});
|
||||
|
||||
describe('packageInfo.buildShaShort', () => {
|
||||
const sha = 'c6e1a25bea71a623929a8f172c0273bf0c811ca0';
|
||||
it('provides the sha and a short version of the sha', () => {
|
||||
mockPackage.raw = {
|
||||
branch: 'some-branch',
|
||||
version: 'some-version',
|
||||
};
|
||||
|
||||
const env = new Env(
|
||||
'/some/home/dir',
|
||||
{
|
||||
branch: 'whathaveyou',
|
||||
version: 'v1',
|
||||
build: {
|
||||
distributable: true,
|
||||
number: 100,
|
||||
sha,
|
||||
date: BUILD_DATE,
|
||||
},
|
||||
},
|
||||
getEnvOptions({
|
||||
cliArgs: { dev: false },
|
||||
configs: ['/some/other/path/some-kibana.yml'],
|
||||
repoPackages: ['FakePackage1', 'FakePackage2'] as unknown as Package[],
|
||||
})
|
||||
);
|
||||
|
||||
expect(env.packageInfo.buildSha).toEqual('c6e1a25bea71a623929a8f172c0273bf0c811ca0');
|
||||
expect(env.packageInfo.buildShaShort).toEqual('c6e1a25bea71');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -121,6 +121,7 @@ export class Env {
|
|||
branch: pkg.branch,
|
||||
buildNum: isKibanaDistributable ? pkg.build.number : Number.MAX_SAFE_INTEGER,
|
||||
buildSha: isKibanaDistributable ? pkg.build.sha : 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
|
||||
buildShaShort: isKibanaDistributable ? pkg.build.sha.slice(0, 12) : 'XXXXXXXXXXXX',
|
||||
version: pkg.version,
|
||||
dist: isKibanaDistributable,
|
||||
buildDate: isKibanaDistributable ? new Date(pkg.build.date) : new Date(),
|
||||
|
|
|
@ -14,6 +14,7 @@ export interface PackageInfo {
|
|||
branch: string;
|
||||
buildNum: number;
|
||||
buildSha: string;
|
||||
buildShaShort: string;
|
||||
buildDate: Date;
|
||||
buildFlavor: BuildFlavor;
|
||||
dist: boolean;
|
||||
|
|
|
@ -17,7 +17,7 @@ import { HttpService } from '@kbn/core-http-server-internal';
|
|||
import { createHttpServer } from '@kbn/core-http-server-mocks';
|
||||
import { registerRouteForBundle, FileHashCache } from '@kbn/core-apps-server-internal';
|
||||
|
||||
const buildNum = 1234;
|
||||
const buildHash = 'buildHash';
|
||||
const fooPluginFixture = resolve(__dirname, './__fixtures__/plugin/foo');
|
||||
|
||||
describe('bundle routes', () => {
|
||||
|
@ -47,8 +47,8 @@ describe('bundle routes', () => {
|
|||
isDist,
|
||||
fileHashCache,
|
||||
bundlesPath: fooPluginFixture,
|
||||
routePath: `/${buildNum}/bundles/plugin/foo/`,
|
||||
publicPath: `/${buildNum}/bundles/plugin/foo/`,
|
||||
routePath: `/${buildHash}/bundles/plugin/foo/`,
|
||||
publicPath: `/${buildHash}/bundles/plugin/foo/`,
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -62,7 +62,7 @@ describe('bundle routes', () => {
|
|||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener)
|
||||
.get(`/${buildNum}/bundles/plugin/foo/image.png`)
|
||||
.get(`/${buildHash}/bundles/plugin/foo/image.png`)
|
||||
.expect(200);
|
||||
|
||||
const actualImage = await readFile(resolve(fooPluginFixture, 'image.png'));
|
||||
|
@ -80,7 +80,7 @@ describe('bundle routes', () => {
|
|||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener)
|
||||
.get(`/${buildNum}/bundles/plugin/foo/plugin.js`)
|
||||
.get(`/${buildHash}/bundles/plugin/foo/plugin.js`)
|
||||
.expect(200);
|
||||
|
||||
const actualFile = await readFile(resolve(fooPluginFixture, 'plugin.js'));
|
||||
|
@ -98,7 +98,7 @@ describe('bundle routes', () => {
|
|||
await server.start();
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get(`/${buildNum}/bundles/plugin/foo/../outside_output.js`)
|
||||
.get(`/${buildHash}/bundles/plugin/foo/../outside_output.js`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
|
@ -112,7 +112,7 @@ describe('bundle routes', () => {
|
|||
await server.start();
|
||||
|
||||
await supertest(innerServer.listener)
|
||||
.get(`/${buildNum}/bundles/plugin/foo/missing.js`)
|
||||
.get(`/${buildHash}/bundles/plugin/foo/missing.js`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
|
@ -126,7 +126,7 @@ describe('bundle routes', () => {
|
|||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener)
|
||||
.get(`/${buildNum}/bundles/plugin/foo/gzip_chunk.js`)
|
||||
.get(`/${buildHash}/bundles/plugin/foo/gzip_chunk.js`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.get('content-encoding')).toEqual('gzip');
|
||||
|
@ -151,7 +151,7 @@ describe('bundle routes', () => {
|
|||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener)
|
||||
.get(`/${buildNum}/bundles/plugin/foo/gzip_chunk.js`)
|
||||
.get(`/${buildHash}/bundles/plugin/foo/gzip_chunk.js`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.get('cache-control')).toEqual('max-age=31536000');
|
||||
|
@ -170,7 +170,7 @@ describe('bundle routes', () => {
|
|||
await server.start();
|
||||
|
||||
const response = await supertest(innerServer.listener)
|
||||
.get(`/${buildNum}/bundles/plugin/foo/gzip_chunk.js`)
|
||||
.get(`/${buildHash}/bundles/plugin/foo/gzip_chunk.js`)
|
||||
.expect(200);
|
||||
|
||||
expect(response.get('cache-control')).toEqual('must-revalidate');
|
||||
|
|
|
@ -11,19 +11,21 @@ import supertest from 'supertest';
|
|||
import moment from 'moment';
|
||||
import { of } from 'rxjs';
|
||||
import { ByteSizeValue } from '@kbn/config-schema';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { Router } from '@kbn/core-http-router-server-internal';
|
||||
import { HttpServer, HttpConfig } from '@kbn/core-http-server-internal';
|
||||
import { mockCoreContext } from '@kbn/core-base-server-mocks';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
|
||||
describe('Http server', () => {
|
||||
let server: HttpServer;
|
||||
let config: HttpConfig;
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
let logger: Logger;
|
||||
let coreContext: ReturnType<typeof mockCoreContext.create>;
|
||||
const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
|
||||
|
||||
beforeEach(() => {
|
||||
const loggingService = loggingSystemMock.create();
|
||||
logger = loggingSystemMock.createLogger();
|
||||
coreContext = mockCoreContext.create();
|
||||
logger = coreContext.logger.get();
|
||||
|
||||
config = {
|
||||
name: 'kibana',
|
||||
|
@ -43,7 +45,7 @@ describe('Http server', () => {
|
|||
shutdownTimeout: moment.duration(5, 's'),
|
||||
} as any;
|
||||
|
||||
server = new HttpServer(loggingService, 'tests', of(config.shutdownTimeout));
|
||||
server = new HttpServer(coreContext, 'tests', of(config.shutdownTimeout));
|
||||
});
|
||||
|
||||
describe('Graceful shutdown', () => {
|
||||
|
|
|
@ -10,7 +10,6 @@ import supertest from 'supertest';
|
|||
import { duration } from 'moment';
|
||||
import { BehaviorSubject, of } from 'rxjs';
|
||||
import { KBN_CERT_PATH, KBN_KEY_PATH, ES_KEY_PATH, ES_CERT_PATH } from '@kbn/dev-utils';
|
||||
import { loggingSystemMock } from '@kbn/core-logging-server-mocks';
|
||||
import { Router } from '@kbn/core-http-router-server-internal';
|
||||
import {
|
||||
HttpServer,
|
||||
|
@ -20,6 +19,8 @@ import {
|
|||
externalUrlConfig,
|
||||
} from '@kbn/core-http-server-internal';
|
||||
import { isServerTLS, flattenCertificateChain, fetchPeerCertificate } from './tls_utils';
|
||||
import { mockCoreContext } from '@kbn/core-base-server-mocks';
|
||||
import type { Logger } from '@kbn/logging';
|
||||
|
||||
const CSP_CONFIG = cspConfig.schema.validate({});
|
||||
const EXTERNAL_URL_CONFIG = externalUrlConfig.schema.validate({});
|
||||
|
@ -27,16 +28,16 @@ const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
|
|||
|
||||
describe('HttpServer - TLS config', () => {
|
||||
let server: HttpServer;
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
let logger: Logger;
|
||||
|
||||
beforeAll(() => {
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
const loggingService = loggingSystemMock.create();
|
||||
logger = loggingSystemMock.createLogger();
|
||||
server = new HttpServer(loggingService, 'tests', of(duration('1s')));
|
||||
const coreContext = mockCoreContext.create();
|
||||
logger = coreContext.logger.get();
|
||||
server = new HttpServer(coreContext, 'tests', of(duration('1s')));
|
||||
});
|
||||
|
||||
it('supports dynamic reloading of the TLS configuration', async () => {
|
||||
|
|
|
@ -83,6 +83,7 @@ describe('GET /api/status', () => {
|
|||
branch: 'xbranch',
|
||||
buildNum: 1234,
|
||||
buildSha: 'xsha',
|
||||
buildShaShort: 'x',
|
||||
dist: true,
|
||||
version: '9.9.9-SNAPSHOT',
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
|
|
|
@ -98,6 +98,7 @@ function pluginInitializerContextMock<T>(config: T = {} as T) {
|
|||
branch: 'branch',
|
||||
buildNum: 100,
|
||||
buildSha: 'buildSha',
|
||||
buildShaShort: 'buildShaShort',
|
||||
dist: false,
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
buildFlavor: 'traditional',
|
||||
|
|
|
@ -157,6 +157,7 @@
|
|||
"@kbn/core-plugins-contracts-server",
|
||||
"@kbn/dev-utils",
|
||||
"@kbn/server-http-tools",
|
||||
"@kbn/core-base-server-mocks",
|
||||
],
|
||||
"exclude": [
|
||||
"target/**/*",
|
||||
|
|
|
@ -14,38 +14,38 @@ export default function ({ getService }) {
|
|||
const supertest = getService('supertest');
|
||||
|
||||
describe('bundle compression', function () {
|
||||
let buildNum;
|
||||
let buildHash;
|
||||
before(async () => {
|
||||
const resp = await supertest.get('/api/status').expect(200);
|
||||
buildNum = resp.body.version.build_number;
|
||||
buildHash = resp.body.version.build_hash.slice(0, 12);
|
||||
});
|
||||
|
||||
it('returns gzip files when client only supports gzip', () =>
|
||||
supertest
|
||||
// We use the kbn-ui-shared-deps for these tests since they are always built with br compressed outputs,
|
||||
// even in dev. Bundles built by @kbn/optimizer are only built with br compression in dist mode.
|
||||
.get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.set('Accept-Encoding', 'gzip')
|
||||
.expect(200)
|
||||
.expect('Content-Encoding', 'gzip'));
|
||||
|
||||
it('returns br files when client only supports br', () =>
|
||||
supertest
|
||||
.get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.set('Accept-Encoding', 'br')
|
||||
.expect(200)
|
||||
.expect('Content-Encoding', 'br'));
|
||||
|
||||
it('returns br files when client only supports gzip and br', () =>
|
||||
supertest
|
||||
.get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.set('Accept-Encoding', 'gzip, br')
|
||||
.expect(200)
|
||||
.expect('Content-Encoding', 'br'));
|
||||
|
||||
it('returns gzip files when client prefers gzip', () =>
|
||||
supertest
|
||||
.get(`/${buildNum}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.get(`/${buildHash}/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.dll.js`)
|
||||
.set('Accept-Encoding', 'gzip;q=1.0, br;q=0.5')
|
||||
.expect(200)
|
||||
.expect('Content-Encoding', 'gzip'));
|
||||
|
|
|
@ -77,7 +77,10 @@ export class CloudFullStoryPlugin implements Plugin {
|
|||
...(pageVarsDebounceTime
|
||||
? { pageVarsDebounceTimeMs: duration(pageVarsDebounceTime).asMilliseconds() }
|
||||
: {}),
|
||||
// Load an Elastic-internally audited script. Ideally, it should be hosted on a CDN.
|
||||
/**
|
||||
* FIXME: this should use the {@link IStaticAssets['getPluginAssetHref']}
|
||||
* function. Then we can avoid registering our own endpoint in this plugin.
|
||||
*/
|
||||
scriptUrl: basePath.prepend(
|
||||
`/internal/cloud/${this.initializerContext.env.packageInfo.buildNum}/fullstory.js`
|
||||
),
|
||||
|
|
|
@ -33,6 +33,7 @@ describe('PdfMaker', () => {
|
|||
branch: 'screenshot-test',
|
||||
buildNum: 567891011,
|
||||
buildSha: 'screenshot-dfdfed0a',
|
||||
buildShaShort: 'scr-dfdfed0a',
|
||||
dist: false,
|
||||
version: '1000.0.0',
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
|
|
|
@ -56,6 +56,7 @@ describe('Screenshot Observable Pipeline', () => {
|
|||
branch: 'screenshot-test',
|
||||
buildNum: 567891011,
|
||||
buildSha: 'screenshot-dfdfed0a',
|
||||
buildShaShort: 'scrn-dfdfed0a',
|
||||
dist: false,
|
||||
version: '5000.0.0',
|
||||
buildDate: new Date('2023-05-15T23:12:09.000Z'),
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`PromptPage renders as expected with additional scripts 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/mock-server-basepath/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/mock-server-basepath/ui/favicons/favicon.svg\\"/><script src=\\"/mock-basepath/some/script1.js\\"></script><script src=\\"/mock-basepath/some/script2.js\\"></script><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">Some Title</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><div>Some Body</div></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#1</span></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#2</span></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
exports[`PromptPage renders as expected with additional scripts 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/ui/favicons/favicon.svg\\"/><script src=\\"/mock-basepath/some/script1.js\\"></script><script src=\\"/mock-basepath/some/script2.js\\"></script><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">Some Title</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><div>Some Body</div></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#1</span></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#2</span></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
|
||||
exports[`PromptPage renders as expected without additional scripts 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/mock-server-basepath/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/mock-server-basepath/ui/favicons/favicon.svg\\"/><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">Some Title</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><div>Some Body</div></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#1</span></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#2</span></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
exports[`PromptPage renders as expected without additional scripts 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/ui/favicons/favicon.svg\\"/><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">Some Title</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><div>Some Body</div></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#1</span></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><span>Action#2</span></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UnauthenticatedPage renders as expected 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/mock-server-basepath/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/mock-server-basepath/ui/favicons/favicon.svg\\"/><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">We hit an authentication error</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Try logging in again, and if the problem persists, contact your system administrator.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/some/url?some-query=some-value#some-hash\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"logInButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in</span></a></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
exports[`UnauthenticatedPage renders as expected 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/ui/favicons/favicon.svg\\"/><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">We hit an authentication error</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Try logging in again, and if the problem persists, contact your system administrator.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/some/url?some-query=some-value#some-hash\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"logInButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in</span></a></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
|
||||
exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"<html lang=\\"en\\"><head><title>My Company Name</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/mock-server-basepath/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/mock-server-basepath/ui/favicons/favicon.svg\\"/><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">We hit an authentication error</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Try logging in again, and if the problem persists, contact your system administrator.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/some/url?some-query=some-value#some-hash\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"logInButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in</span></a></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
exports[`UnauthenticatedPage renders as expected with custom title 1`] = `"<html lang=\\"en\\"><head><title>My Company Name</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/ui/favicons/favicon.svg\\"/><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">We hit an authentication error</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Try logging in again, and if the problem persists, contact your system administrator.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/some/url?some-query=some-value#some-hash\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"logInButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in</span></a></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
|
|
|
@ -19,6 +19,7 @@ import type {
|
|||
ElasticsearchServiceSetup,
|
||||
HttpServiceSetup,
|
||||
HttpServiceStart,
|
||||
IStaticAssets,
|
||||
KibanaRequest,
|
||||
Logger,
|
||||
LoggerFactory,
|
||||
|
@ -63,7 +64,7 @@ describe('AuthenticationService', () => {
|
|||
elasticsearch: jest.Mocked<ElasticsearchServiceSetup>;
|
||||
config: ConfigType;
|
||||
license: jest.Mocked<SecurityLicense>;
|
||||
buildNumber: number;
|
||||
staticAssets: IStaticAssets;
|
||||
customBranding: jest.Mocked<CustomBrandingSetup>;
|
||||
};
|
||||
let mockStartAuthenticationParams: {
|
||||
|
@ -96,7 +97,7 @@ describe('AuthenticationService', () => {
|
|||
isTLSEnabled: false,
|
||||
}),
|
||||
license: licenseMock.create(),
|
||||
buildNumber: 100500,
|
||||
staticAssets: coreSetupMock.http.staticAssets,
|
||||
customBranding: customBrandingServiceMock.createSetupContract(),
|
||||
};
|
||||
mockCanRedirectRequest.mockReturnValue(false);
|
||||
|
@ -983,7 +984,7 @@ describe('AuthenticationService', () => {
|
|||
|
||||
expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({
|
||||
basePath: mockSetupAuthenticationParams.http.basePath,
|
||||
buildNumber: 100500,
|
||||
staticAssets: expect.any(Object),
|
||||
originalURL: '/mock-server-basepath/app/some',
|
||||
});
|
||||
});
|
||||
|
@ -1015,7 +1016,7 @@ describe('AuthenticationService', () => {
|
|||
|
||||
expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({
|
||||
basePath: mockSetupAuthenticationParams.http.basePath,
|
||||
buildNumber: 100500,
|
||||
staticAssets: expect.any(Object),
|
||||
originalURL: '/mock-server-basepath/app/some',
|
||||
});
|
||||
});
|
||||
|
@ -1050,7 +1051,7 @@ describe('AuthenticationService', () => {
|
|||
|
||||
expect(mockRenderUnauthorizedPage).toHaveBeenCalledWith({
|
||||
basePath: mockSetupAuthenticationParams.http.basePath,
|
||||
buildNumber: 100500,
|
||||
staticAssets: expect.any(Object),
|
||||
originalURL: '/mock-server-basepath/',
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,12 +40,14 @@ import type { Session } from '../session_management';
|
|||
import type { UserProfileServiceStartInternal } from '../user_profile';
|
||||
|
||||
interface AuthenticationServiceSetupParams {
|
||||
http: Pick<HttpServiceSetup, 'basePath' | 'csp' | 'registerAuth' | 'registerOnPreResponse'>;
|
||||
http: Pick<
|
||||
HttpServiceSetup,
|
||||
'basePath' | 'csp' | 'registerAuth' | 'registerOnPreResponse' | 'staticAssets'
|
||||
>;
|
||||
customBranding: CustomBrandingSetup;
|
||||
elasticsearch: Pick<ElasticsearchServiceSetup, 'setUnauthorizedErrorHandler'>;
|
||||
config: ConfigType;
|
||||
license: SecurityLicense;
|
||||
buildNumber: number;
|
||||
}
|
||||
|
||||
interface AuthenticationServiceStartParams {
|
||||
|
@ -92,7 +94,6 @@ export class AuthenticationService {
|
|||
config,
|
||||
http,
|
||||
license,
|
||||
buildNumber,
|
||||
elasticsearch,
|
||||
customBranding,
|
||||
}: AuthenticationServiceSetupParams) {
|
||||
|
@ -204,8 +205,8 @@ export class AuthenticationService {
|
|||
});
|
||||
return toolkit.render({
|
||||
body: renderUnauthenticatedPage({
|
||||
buildNumber,
|
||||
basePath: http.basePath,
|
||||
staticAssets: http.staticAssets,
|
||||
originalURL,
|
||||
customBranding: customBrandingValue,
|
||||
}),
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('UnauthenticatedPage', () => {
|
|||
const body = renderToStaticMarkup(
|
||||
<UnauthenticatedPage
|
||||
originalURL="/some/url?some-query=some-value#some-hash"
|
||||
buildNumber={100500}
|
||||
staticAssets={mockCoreSetup.http.staticAssets}
|
||||
basePath={mockCoreSetup.http.basePath}
|
||||
customBranding={{}}
|
||||
/>
|
||||
|
@ -44,7 +44,7 @@ describe('UnauthenticatedPage', () => {
|
|||
const body = renderToStaticMarkup(
|
||||
<UnauthenticatedPage
|
||||
originalURL="/some/url?some-query=some-value#some-hash"
|
||||
buildNumber={100500}
|
||||
staticAssets={mockCoreSetup.http.staticAssets}
|
||||
basePath={mockCoreSetup.http.basePath}
|
||||
customBranding={{ pageTitle: 'My Company Name' }}
|
||||
/>
|
||||
|
|
|
@ -11,6 +11,7 @@ import { renderToStaticMarkup } from 'react-dom/server';
|
|||
|
||||
import type { IBasePath } from '@kbn/core/server';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { IStaticAssets } from '@kbn/core-http-server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
|
@ -18,16 +19,21 @@ import { PromptPage } from '../prompt_page';
|
|||
|
||||
interface Props {
|
||||
originalURL: string;
|
||||
buildNumber: number;
|
||||
basePath: IBasePath;
|
||||
staticAssets: IStaticAssets;
|
||||
customBranding: CustomBranding;
|
||||
}
|
||||
|
||||
export function UnauthenticatedPage({ basePath, originalURL, buildNumber, customBranding }: Props) {
|
||||
export function UnauthenticatedPage({
|
||||
basePath,
|
||||
originalURL,
|
||||
staticAssets,
|
||||
customBranding,
|
||||
}: Props) {
|
||||
return (
|
||||
<PromptPage
|
||||
buildNumber={buildNumber}
|
||||
basePath={basePath}
|
||||
staticAssets={staticAssets}
|
||||
title={i18n.translate('xpack.security.unauthenticated.pageTitle', {
|
||||
defaultMessage: 'We hit an authentication error',
|
||||
})}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ResetSessionPage renders as expected 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/mock-server-basepath/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/mock-server-basepath/ui/favicons/favicon.svg\\"/><script src=\\"/mock-basepath/internal/security/reset_session_page.js\\"></script><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">You do not have permission to access the requested page</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Either go back to the previous page or log in as a different user.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/path/to/logout\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"ResetSessionButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in as different user</span></a></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonEmpty eui-9ywwuh-euiButtonDisplay-euiButtonEmpty-m-empty-primary\\" type=\\"button\\" id=\\"goBackButton\\"><span class=\\"euiButtonEmpty__content eui-1bascr2-euiButtonDisplayContent\\"><span class=\\"eui-textTruncate euiButtonEmpty__text\\">Go back</span></span></button></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
exports[`ResetSessionPage renders as expected 1`] = `"<html lang=\\"en\\"><head><title>Elastic</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/ui/favicons/favicon.svg\\"/><script src=\\"/mock-basepath/internal/security/reset_session_page.js\\"></script><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">You do not have permission to access the requested page</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Either go back to the previous page or log in as a different user.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/path/to/logout\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"ResetSessionButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in as different user</span></a></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonEmpty eui-9ywwuh-euiButtonDisplay-euiButtonEmpty-m-empty-primary\\" type=\\"button\\" id=\\"goBackButton\\"><span class=\\"euiButtonEmpty__content eui-1bascr2-euiButtonDisplayContent\\"><span class=\\"eui-textTruncate euiButtonEmpty__text\\">Go back</span></span></button></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
|
||||
exports[`ResetSessionPage renders as expected with custom page title 1`] = `"<html lang=\\"en\\"><head><title>My Company Name</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/mock-server-basepath/100500/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/mock-server-basepath/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/mock-server-basepath/ui/favicons/favicon.svg\\"/><script src=\\"/mock-basepath/internal/security/reset_session_page.js\\"></script><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">You do not have permission to access the requested page</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Either go back to the previous page or log in as a different user.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/path/to/logout\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"ResetSessionButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in as different user</span></a></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonEmpty eui-9ywwuh-euiButtonDisplay-euiButtonEmpty-m-empty-primary\\" type=\\"button\\" id=\\"goBackButton\\"><span class=\\"euiButtonEmpty__content eui-1bascr2-euiButtonDisplayContent\\"><span class=\\"eui-textTruncate euiButtonEmpty__text\\">Go back</span></span></button></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
exports[`ResetSessionPage renders as expected with custom page title 1`] = `"<html lang=\\"en\\"><head><title>My Company Name</title><style></style><style data-emotion=\\"eui \\"></style></style><link href=\\"/bundles/kbn-ui-shared-deps-src/kbn-ui-shared-deps-src.css\\" rel=\\"stylesheet\\"/><link href=\\"/bundles/kbn-ui-shared-deps-npm/kbn-ui-shared-deps-npm.v8.light.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"alternate icon\\" type=\\"image/png\\" href=\\"/ui/favicons/favicon.png\\"/><link rel=\\"icon\\" type=\\"image/svg+xml\\" href=\\"/ui/favicons/favicon.svg\\"/><script src=\\"/mock-basepath/internal/security/reset_session_page.js\\"></script><meta name=\\"theme-color\\" content=\\"#ffffff\\"/><meta name=\\"color-scheme\\" content=\\"light dark\\"/></head><body><div data-test-subj=\\"promptPage\\" style=\\"min-block-size:max(460px, 100vh);padding-block-start:var(--euiFixedHeadersOffset, 0)\\" class=\\"euiPageTemplate eui-cjgvy1-euiPageOuter-row-grow\\"><main id=\\"EuiPageTemplateInner_generated-id\\" class=\\"eui-nq554q-euiPageInner\\"><section class=\\"eui-68douo-euiPageSection-grow-l-center-transparent\\"><div class=\\"eui-1sghhs8-euiPageSection__content-l-center\\"><div class=\\"euiPanel euiPanel--plain euiEmptyPrompt euiEmptyPrompt--vertical euiEmptyPrompt--paddingLarge eui-12g67tv-euiPanel-m-plain-hasShadow\\"><div class=\\"euiEmptyPrompt__main\\"><div class=\\"euiEmptyPrompt__icon\\"><span data-euiicon-type=\\"warning\\" color=\\"danger\\"></span></div><div class=\\"euiEmptyPrompt__content\\"><div class=\\"euiEmptyPrompt__contentInner\\"><h2 class=\\"euiTitle eui-smz32e-euiTitle-m\\">You do not have permission to access the requested page</h2><div class=\\"euiSpacer euiSpacer--m eui-jv9za2-euiSpacer-m\\"></div><div class=\\"euiText eui-eqvwj3-euiText-m-euiTextColor-subdued\\"><p>Either go back to the previous page or log in as a different user.</p></div><div class=\\"euiSpacer euiSpacer--l eui-p2o3x6-euiSpacer-l\\"></div><div class=\\"euiFlexGroup euiEmptyPrompt__actions eui-12cw070-euiFlexGroup-m-center-center-column\\"><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><a href=\\"/path/to/logout\\" rel=\\"noreferrer\\" class=\\"euiButton eui-8utmkn-euiButtonDisplay-m-defaultMinWidth-fill-primary\\" data-test-subj=\\"ResetSessionButton\\"><span class=\\"eui-1bascr2-euiButtonDisplayContent\\">Log in as different user</span></a></div><div class=\\"euiFlexItem eui-kpsrin-euiFlexItem-growZero\\"><button class=\\"euiButtonEmpty eui-9ywwuh-euiButtonDisplay-euiButtonEmpty-m-empty-primary\\" type=\\"button\\" id=\\"goBackButton\\"><span class=\\"euiButtonEmpty__content eui-1bascr2-euiButtonDisplayContent\\"><span class=\\"eui-textTruncate euiButtonEmpty__text\\">Go back</span></span></button></div></div></div></div></div></div></div></section></main></div></body></html>"`;
|
||||
|
|
|
@ -76,7 +76,6 @@ it(`#setup returns exposed services`, () => {
|
|||
loggers: loggingSystemMock.create(),
|
||||
kibanaIndexName,
|
||||
packageVersion: 'some-version',
|
||||
buildNumber: 42,
|
||||
features: mockFeaturesSetup,
|
||||
getSpacesService: mockGetSpacesService,
|
||||
getCurrentUser: jest.fn(),
|
||||
|
@ -138,7 +137,6 @@ describe('#start', () => {
|
|||
loggers: loggingSystemMock.create(),
|
||||
kibanaIndexName,
|
||||
packageVersion: 'some-version',
|
||||
buildNumber: 42,
|
||||
features: featuresPluginMock.createSetup(),
|
||||
getSpacesService: jest
|
||||
.fn()
|
||||
|
@ -211,7 +209,6 @@ it('#stop unsubscribes from license and ES updates.', async () => {
|
|||
loggers: loggingSystemMock.create(),
|
||||
kibanaIndexName,
|
||||
packageVersion: 'some-version',
|
||||
buildNumber: 42,
|
||||
features: featuresPluginMock.createSetup(),
|
||||
getSpacesService: jest
|
||||
.fn()
|
||||
|
|
|
@ -57,7 +57,6 @@ export { Actions } from './actions';
|
|||
|
||||
interface AuthorizationServiceSetupParams {
|
||||
packageVersion: string;
|
||||
buildNumber: number;
|
||||
http: HttpServiceSetup;
|
||||
capabilities: CapabilitiesSetup;
|
||||
getClusterClient: () => Promise<IClusterClient>;
|
||||
|
@ -100,7 +99,6 @@ export class AuthorizationService {
|
|||
http,
|
||||
capabilities,
|
||||
packageVersion,
|
||||
buildNumber,
|
||||
getClusterClient,
|
||||
license,
|
||||
loggers,
|
||||
|
@ -179,7 +177,7 @@ export class AuthorizationService {
|
|||
const next = `${http.basePath.get(request)}${request.url.pathname}${request.url.search}`;
|
||||
const body = renderToString(
|
||||
<ResetSessionPage
|
||||
buildNumber={buildNumber}
|
||||
staticAssets={http.staticAssets}
|
||||
basePath={http.basePath}
|
||||
logoutUrl={http.basePath.prepend(
|
||||
`/api/security/logout?${querystring.stringify({ next })}`
|
||||
|
|
|
@ -26,7 +26,7 @@ describe('ResetSessionPage', () => {
|
|||
const body = renderToStaticMarkup(
|
||||
<ResetSessionPage
|
||||
logoutUrl="/path/to/logout"
|
||||
buildNumber={100500}
|
||||
staticAssets={mockCoreSetup.http.staticAssets}
|
||||
basePath={mockCoreSetup.http.basePath}
|
||||
customBranding={{}}
|
||||
/>
|
||||
|
@ -44,7 +44,7 @@ describe('ResetSessionPage', () => {
|
|||
const body = renderToStaticMarkup(
|
||||
<ResetSessionPage
|
||||
logoutUrl="/path/to/logout"
|
||||
buildNumber={100500}
|
||||
staticAssets={mockCoreSetup.http.staticAssets}
|
||||
basePath={mockCoreSetup.http.basePath}
|
||||
customBranding={{ pageTitle: 'My Company Name' }}
|
||||
/>
|
||||
|
|
|
@ -10,6 +10,7 @@ import React from 'react';
|
|||
|
||||
import type { IBasePath } from '@kbn/core/server';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { IStaticAssets } from '@kbn/core-http-server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
|
||||
|
@ -22,18 +23,18 @@ import { PromptPage } from '../prompt_page';
|
|||
*/
|
||||
export function ResetSessionPage({
|
||||
logoutUrl,
|
||||
buildNumber,
|
||||
staticAssets,
|
||||
basePath,
|
||||
customBranding,
|
||||
}: {
|
||||
logoutUrl: string;
|
||||
buildNumber: number;
|
||||
staticAssets: IStaticAssets;
|
||||
basePath: IBasePath;
|
||||
customBranding: CustomBranding;
|
||||
}) {
|
||||
return (
|
||||
<PromptPage
|
||||
buildNumber={buildNumber}
|
||||
staticAssets={staticAssets}
|
||||
basePath={basePath}
|
||||
scriptPaths={['/internal/security/reset_session_page.js']}
|
||||
title={i18n.translate('xpack.security.resetSession.title', {
|
||||
|
|
|
@ -255,7 +255,6 @@ export class SecurityPlugin
|
|||
elasticsearch: core.elasticsearch,
|
||||
config,
|
||||
license,
|
||||
buildNumber: this.initializerContext.env.packageInfo.buildNum,
|
||||
customBranding: core.customBranding,
|
||||
});
|
||||
|
||||
|
@ -283,7 +282,6 @@ export class SecurityPlugin
|
|||
loggers: this.initializerContext.logger,
|
||||
kibanaIndexName,
|
||||
packageVersion: this.initializerContext.env.packageInfo.version,
|
||||
buildNumber: this.initializerContext.env.packageInfo.buildNum,
|
||||
getSpacesService: () => spaces?.spacesService,
|
||||
features,
|
||||
getCurrentUser: (request) => this.getAuthentication().getCurrentUser(request),
|
||||
|
|
|
@ -25,7 +25,7 @@ describe('PromptPage', () => {
|
|||
|
||||
const body = renderToStaticMarkup(
|
||||
<PromptPage
|
||||
buildNumber={100500}
|
||||
staticAssets={mockCoreSetup.http.staticAssets}
|
||||
basePath={mockCoreSetup.http.basePath}
|
||||
title="Some Title"
|
||||
body={<div>Some Body</div>}
|
||||
|
@ -45,7 +45,7 @@ describe('PromptPage', () => {
|
|||
|
||||
const body = renderToStaticMarkup(
|
||||
<PromptPage
|
||||
buildNumber={100500}
|
||||
staticAssets={mockCoreSetup.http.staticAssets}
|
||||
basePath={mockCoreSetup.http.basePath}
|
||||
scriptPaths={['/some/script1.js', '/some/script2.js']}
|
||||
title="Some Title"
|
||||
|
|
|
@ -19,6 +19,7 @@ import { renderToString } from 'react-dom/server';
|
|||
|
||||
import type { IBasePath } from '@kbn/core/server';
|
||||
import type { CustomBranding } from '@kbn/core-custom-branding-common';
|
||||
import type { IStaticAssets } from '@kbn/core-http-server';
|
||||
import { Fonts } from '@kbn/core-rendering-server-internal';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { I18nProvider } from '@kbn/i18n-react';
|
||||
|
@ -35,7 +36,7 @@ appendIconComponentCache({
|
|||
const emotionCache = createCache({ key: 'eui', stylisPlugins: [euiStylisPrefixer] });
|
||||
|
||||
interface Props {
|
||||
buildNumber: number;
|
||||
staticAssets: IStaticAssets;
|
||||
basePath: IBasePath;
|
||||
scriptPaths?: string[];
|
||||
title: ReactNode;
|
||||
|
@ -46,7 +47,7 @@ interface Props {
|
|||
|
||||
export function PromptPage({
|
||||
basePath,
|
||||
buildNumber,
|
||||
staticAssets,
|
||||
scriptPaths = [],
|
||||
title,
|
||||
body,
|
||||
|
@ -74,8 +75,8 @@ export function PromptPage({
|
|||
const chunks = extractCriticalToChunks(renderToString(content));
|
||||
const emotionStyles = constructStyleTagsFromChunks(chunks);
|
||||
|
||||
const uiPublicURL = `${basePath.serverBasePath}/ui`;
|
||||
const regularBundlePath = `${basePath.serverBasePath}/${buildNumber}/bundles`;
|
||||
const uiPublicURL = staticAssets.prependPublicUrl('/ui');
|
||||
const regularBundlePath = staticAssets.prependPublicUrl('/bundles');
|
||||
const styleSheetPaths = [
|
||||
`${regularBundlePath}/kbn-ui-shared-deps-src/${UiSharedDepsSrc.cssDistFilename}`,
|
||||
`${regularBundlePath}/kbn-ui-shared-deps-npm/${UiSharedDepsNpm.lightCssDistFilename('v8')}`,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue