Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/development/core/server/kibana-plugin-server.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [SavedObjectsBulkCreateObject](./kibana-plugin-server.savedobjectsbulkcreateobject.md) | |
| [SavedObjectsBulkGetObject](./kibana-plugin-server.savedobjectsbulkgetobject.md) | |
| [SavedObjectsBulkResponse](./kibana-plugin-server.savedobjectsbulkresponse.md) | |
| [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) | |
| [SavedObjectsCreateOptions](./kibana-plugin-server.savedobjectscreateoptions.md) | |
| [SavedObjectsFindOptions](./kibana-plugin-server.savedobjectsfindoptions.md) | |
| [SavedObjectsFindResponse](./kibana-plugin-server.savedobjectsfindresponse.md) | |
Expand Down Expand Up @@ -81,4 +82,5 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [RecursiveReadonly](./kibana-plugin-server.recursivereadonly.md) | |
| [RouteMethod](./kibana-plugin-server.routemethod.md) | The set of common HTTP methods supported by Kibana routing. |
| [SavedObjectsClientContract](./kibana-plugin-server.savedobjectsclientcontract.md) | \#\# SavedObjectsClient errors<!-- -->Since the SavedObjectsClient has its hands in everything we are a little paranoid about the way we present errors back to to application code. Ideally, all errors will be either:<!-- -->1. Caused by bad implementation (ie. undefined is not a function) and as such unpredictable 2. An error that has been classified and decorated appropriately by the decorators in [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md)<!-- -->Type 1 errors are inevitable, but since all expected/handle-able errors should be Type 2 the <code>isXYZError()</code> helpers exposed at <code>SavedObjectsErrorHelpers</code> should be used to understand and manage error responses from the <code>SavedObjectsClient</code>.<!-- -->Type 2 errors are decorated versions of the source error, so if the elasticsearch client threw an error it will be decorated based on its type. That means that rather than looking for <code>error.body.error.type</code> or doing substring checks on <code>error.body.error.reason</code>, just use the helpers to understand the meaning of the error:<!-- -->\`\`\`<!-- -->js if (SavedObjectsErrorHelpers.isNotFoundError(error)) { // handle 404 }<!-- -->if (SavedObjectsErrorHelpers.isNotAuthorizedError(error)) { // 401 handling should be automatic, but in case you wanted to know }<!-- -->// always rethrow the error unless you handle it throw error; \`\`\`<!-- -->\#\#\# 404s from missing index<!-- -->From the perspective of application code and APIs the SavedObjectsClient is a black box that persists objects. One of the internal details that users have no control over is that we use an elasticsearch index for persistance and that index might be missing.<!-- -->At the time of writing we are in the process of transitioning away from the operating assumption that the SavedObjects index is always available. Part of this transition is handling errors resulting from an index missing. These used to trigger a 500 error in most cases, and in others cause 404s with different error messages.<!-- -->From my (Spencer) perspective, a 404 from the SavedObjectsApi is a 404; The object the request/call was targeting could not be found. This is why \#14141 takes special care to ensure that 404 errors are generic and don't distinguish between index missing or document missing.<!-- -->\#\#\# 503s from missing index<!-- -->Unlike all other methods, create requests are supposed to succeed even when the Kibana index does not exist because it will be automatically created by elasticsearch. When that is not the case it is because Elasticsearch's <code>action.auto_create_index</code> setting prevents it from being created automatically so we throw a special 503 with the intention of informing the user that their Elasticsearch settings need to be updated.<!-- -->See [SavedObjectsErrorHelpers](./kibana-plugin-server.savedobjectserrorhelpers.md) |
| [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md) | |

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [SavedObjectsClientWrapperFactory](./kibana-plugin-server.savedobjectsclientwrapperfactory.md)

## SavedObjectsClientWrapperFactory type

<b>Signature:</b>

```typescript
export declare type SavedObjectsClientWrapperFactory<Request = unknown> = (options: SavedObjectsClientWrapperOptions<Request>) => SavedObjectsClientContract;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) &gt; [client](./kibana-plugin-server.savedobjectsclientwrapperoptions.client.md)

## SavedObjectsClientWrapperOptions.client property

<b>Signature:</b>

```typescript
client: SavedObjectsClientContract;
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md)

## SavedObjectsClientWrapperOptions interface

<b>Signature:</b>

```typescript
export interface SavedObjectsClientWrapperOptions<Request = unknown>
```

## Properties

| Property | Type | Description |
| --- | --- | --- |
| [client](./kibana-plugin-server.savedobjectsclientwrapperoptions.client.md) | <code>SavedObjectsClientContract</code> | |
| [request](./kibana-plugin-server.savedobjectsclientwrapperoptions.request.md) | <code>Request</code> | |

Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [SavedObjectsClientWrapperOptions](./kibana-plugin-server.savedobjectsclientwrapperoptions.md) &gt; [request](./kibana-plugin-server.savedobjectsclientwrapperoptions.request.md)

## SavedObjectsClientWrapperOptions.request property

<b>Signature:</b>

```typescript
request: Request;
```
5 changes: 4 additions & 1 deletion src/core/server/elasticsearch/scoped_cluster_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ export class ScopedClusterClient {
private readonly internalAPICaller: APICaller,
private readonly scopedAPICaller: APICaller,
private readonly headers?: Headers
) {}
) {
this.callAsCurrentUser = this.callAsCurrentUser.bind(this);
this.callAsInternalUser = this.callAsInternalUser.bind(this);
}

/**
* Calls specified `endpoint` with provided `clientParams` on behalf of the
Expand Down
2 changes: 2 additions & 0 deletions src/core/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ export {
SavedObjectsClient,
SavedObjectsClientContract,
SavedObjectsCreateOptions,
SavedObjectsClientWrapperFactory,
SavedObjectsClientWrapperOptions,
SavedObjectsErrorHelpers,
SavedObjectsFindOptions,
SavedObjectsFindResponse,
Expand Down
1 change: 1 addition & 0 deletions src/core/server/saved_objects/service/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export {
SavedObjectsRepository,
ScopedSavedObjectsClientProvider,
SavedObjectsClientWrapperFactory,
SavedObjectsClientWrapperOptions,
SavedObjectsErrorHelpers,
} from './lib';

Expand Down
15 changes: 15 additions & 0 deletions src/core/server/server.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,21 @@ export class SavedObjectsClient {
// @public
export type SavedObjectsClientContract = Pick<SavedObjectsClient, keyof SavedObjectsClient>;

// Warning: (ae-missing-release-tag) "SavedObjectsClientWrapperFactory" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export type SavedObjectsClientWrapperFactory<Request = unknown> = (options: SavedObjectsClientWrapperOptions<Request>) => SavedObjectsClientContract;

// Warning: (ae-missing-release-tag) "SavedObjectsClientWrapperOptions" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
export interface SavedObjectsClientWrapperOptions<Request = unknown> {
// (undocumented)
client: SavedObjectsClientContract;
// (undocumented)
request: Request;
}

// @public (undocumented)
export interface SavedObjectsCreateOptions extends SavedObjectsBaseOptions {
id?: string;
Expand Down
5 changes: 0 additions & 5 deletions src/legacy/server/http/setup_base_path_provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,6 @@
*/

export function setupBasePathProvider(kbnServer) {
kbnServer.server.decorate('request', 'setBasePath', function (basePath) {
const request = this;
kbnServer.newPlatform.setup.core.http.basePath.set(request, basePath);
});

kbnServer.server.decorate('request', 'getBasePath', function () {
const request = this;
return kbnServer.newPlatform.setup.core.http.basePath.get(request);
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/security/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ import {
import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize';
import { SecureSavedObjectsClientWrapper } from './server/lib/saved_objects_client/secure_saved_objects_client_wrapper';
import { deepFreeze } from './server/lib/deep_freeze';
import { createOptionalPlugin } from './server/lib/optional_plugin';
import { createOptionalPlugin } from '../../server/lib/optional_plugin';

export const security = (kibana) => new kibana.Plugin({
id: 'security',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
*/

import { SpacesPlugin } from '../../../../spaces/types';
import { OptionalPlugin } from '../optional_plugin';
import { OptionalPlugin } from '../../../../../server/lib/optional_plugin';
import { checkPrivilegesDynamicallyWithRequestFactory } from './check_privileges_dynamically';

test(`checkPrivileges.atSpace when spaces is enabled`, async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { CheckPrivilegesAtResourceResponse, CheckPrivilegesWithRequest } from '.
*/

import { SpacesPlugin } from '../../../../spaces/types';
import { OptionalPlugin } from '../optional_plugin';
import { OptionalPlugin } from '../../../../../server/lib/optional_plugin';

export type CheckPrivilegesDynamically = (
privilegeOrPrivileges: string | string[]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { getClient } from '../../../../../server/lib/get_client_shield';
import { SpacesPlugin } from '../../../../spaces/types';
import { XPackFeature, XPackMainPlugin } from '../../../../xpack_main/xpack_main';
import { APPLICATION_PREFIX } from '../../../common/constants';
import { OptionalPlugin } from '../optional_plugin';
import { OptionalPlugin } from '../../../../../server/lib/optional_plugin';
import { Actions, actionsFactory } from './actions';
import { CheckPrivilegesWithRequest, checkPrivilegesWithRequestFactory } from './check_privileges';
import {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Boom from 'boom';
import { Server } from 'hapi';
import { RawKibanaPrivileges } from '../../../../../common/model';
import { initGetPrivilegesApi } from './get';
import { AuthorizationService } from '../../../../lib/authorization/service';

const createRawKibanaPrivileges: () => RawKibanaPrivileges = () => {
return {
Expand Down Expand Up @@ -37,13 +38,13 @@ const createMockServer = () => {
const mockServer = new Server({ debug: false, port: 8080 });

mockServer.plugins.security = {
authorization: {
authorization: ({
privileges: {
get: jest.fn().mockImplementation(() => {
return createRawKibanaPrivileges();
}),
},
},
} as unknown) as AuthorizationService,
} as any;
return mockServer;
};
Expand Down
168 changes: 76 additions & 92 deletions x-pack/plugins/spaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,25 @@
* you may not use this file except in compliance with the Elastic License.
*/

import * as Rx from 'rxjs';
import { resolve } from 'path';

import { SavedObjectsService } from 'src/core/server';
import { Request, Server } from 'hapi';
import KbnServer, { Server } from 'src/legacy/server/kbn_server';
import { createOptionalPlugin } from '../../server/lib/optional_plugin';
// @ts-ignore
import { AuditLogger } from '../../server/lib/audit_logger';
// @ts-ignore
import { watchStatusAndLicenseToInitialize } from '../../server/lib/watch_status_and_license_to_initialize';
import mappings from './mappings.json';
import { SpacesAuditLogger } from './server/lib/audit_logger';
import { checkLicense } from './server/lib/check_license';
import { createDefaultSpace } from './server/lib/create_default_space';
import { createSpacesService } from './server/lib/create_spaces_service';
import { wrapError } from './server/lib/errors';
import { getActiveSpace } from './server/lib/get_active_space';
import { getSpaceSelectorUrl } from './server/lib/get_space_selector_url';
import { getSpacesUsageCollector } from './server/lib/get_spaces_usage_collector';
import { migrateToKibana660 } from './server/lib/migrations';
import { initSpacesRequestInterceptors } from './server/lib/request_inteceptors';
import { spacesSavedObjectsClientWrapperFactory } from './server/lib/saved_objects_client/saved_objects_client_wrapper_factory';
import { SpacesClient } from './server/lib/spaces_client';
import { createSpacesTutorialContextFactory } from './server/lib/spaces_tutorial_context_factory';
import { toggleUICapabilities } from './server/lib/toggle_ui_capabilities';
import { initExternalSpacesApi } from './server/routes/api/external';
import { initInternalApis } from './server/routes/api/v1';

import { plugin } from './server/new_platform';
import {
SpacesInitializerContext,
SpacesCoreSetup,
SpacesHttpServiceSetup,
} from './server/new_platform/plugin';
import { initSpacesRequestInterceptors } from './server/lib/request_interceptors';
import { SecurityPlugin } from '../security';
export const spaces = (kibana: Record<string, any>) =>
new kibana.Plugin({
id: 'spaces',
Expand Down Expand Up @@ -95,7 +88,7 @@ export const spaces = (kibana: Record<string, any>) =>
request: Record<string, any>,
server: Record<string, any>
) {
const spacesClient = server.plugins.spaces.spacesClient.getScopedClient(request);
const spacesClient = await server.plugins.spaces.getScopedSpacesClient(request);
try {
vars.activeSpace = {
valid: true,
Expand All @@ -117,86 +110,77 @@ export const spaces = (kibana: Record<string, any>) =>
},

async init(server: Server) {
const thisPlugin = this;
const xpackMainPlugin = server.plugins.xpack_main;

watchStatusAndLicenseToInitialize(xpackMainPlugin, thisPlugin, async () => {
await createDefaultSpace(server);
});
const kbnServer = (server as unknown) as KbnServer;
const initializerContext = ({
legacyConfig: server.config(),
config: {
create: () => {
return Rx.of({
maxSpaces: server.config().get('xpack.spaces.maxSpaces'),
});
},
},
logger: {
get(...contextParts: string[]) {
return kbnServer.newPlatform.coreContext.logger.get(
'plugins',
'spaces',
...contextParts
);
},
},
} as unknown) as SpacesInitializerContext;

// Register a function that is called whenever the xpack info changes,
// to re-compute the license check results for this plugin.
xpackMainPlugin.info
.feature(thisPlugin.id)
.registerLicenseCheckResultsGenerator(checkLicense);
const spacesHttpService: SpacesHttpServiceSetup = {
...kbnServer.newPlatform.setup.core.http,
route: server.route.bind(server),
};

const spacesService = createSpacesService(server);
server.expose('getSpaceId', (request: any) => spacesService.getSpaceId(request));
const core: SpacesCoreSetup = {
http: spacesHttpService,
elasticsearch: kbnServer.newPlatform.setup.core.elasticsearch,
savedObjects: server.savedObjects,
usage: server.usage,
tutorial: {
addScopedTutorialContextFactory: server.addScopedTutorialContextFactory,
},
capabilities: {
registerCapabilitiesModifier: server.registerCapabilitiesModifier,
},
auditLogger: {
create: (pluginId: string) =>
new AuditLogger(server, pluginId, server.config(), server.plugins.xpack_main.info),
},
};

const config = server.config();
const plugins = {
xpackMain: server.plugins.xpack_main,
// TODO: Spaces has a circular dependency with Security right now.
// Security is not yet available when init runs, so this is wrapped in an optional function for the time being.
security: createOptionalPlugin<SecurityPlugin>(
server.config(),
'xpack.security',
server.plugins,
'security'
),
spaces: this,
};

const spacesAuditLogger = new SpacesAuditLogger(
new AuditLogger(server, 'spaces', config, xpackMainPlugin.info)
);
const { spacesService, log } = await plugin(initializerContext).setup(core, plugins);

server.expose('spacesClient', {
getScopedClient: (request: Request) => {
const adminCluster = server.plugins.elasticsearch.getCluster('admin');
const { callWithRequest, callWithInternalUser } = adminCluster;
const callCluster = callWithRequest.bind(adminCluster, request);
const { savedObjects } = server;
const internalRepository = savedObjects.getSavedObjectsRepository(callWithInternalUser);
const callWithRequestRepository = savedObjects.getSavedObjectsRepository(callCluster);
const authorization = server.plugins.security
? server.plugins.security.authorization
: null;
return new SpacesClient(
spacesAuditLogger,
(message: string) => {
server.log(['spaces', 'debug'], message);
},
authorization,
callWithRequestRepository,
server.config(),
internalRepository,
request
);
initSpacesRequestInterceptors({
config: initializerContext.legacyConfig,
http: core.http,
getHiddenUiAppById: server.getHiddenUiAppById,
onPostAuth: handler => {
server.ext('onPostAuth', handler);
},
log,
spacesService,
xpackMain: plugins.xpackMain,
});

const {
addScopedSavedObjectsClientWrapperFactory,
types,
} = server.savedObjects as SavedObjectsService;
addScopedSavedObjectsClientWrapperFactory(
Number.MAX_SAFE_INTEGER - 1,
spacesSavedObjectsClientWrapperFactory(spacesService, types)
);

server.addScopedTutorialContextFactory(createSpacesTutorialContextFactory(spacesService));

initInternalApis(server);
initExternalSpacesApi(server);

initSpacesRequestInterceptors(server);

// Register a function with server to manage the collection of usage stats
server.usage.collectorSet.register(getSpacesUsageCollector(server));

server.registerCapabilitiesModifier(async (request, uiCapabilities) => {
const spacesClient = server.plugins.spaces.spacesClient.getScopedClient(request);
try {
const activeSpace = await getActiveSpace(
spacesClient,
request.getBasePath(),
server.config().get('server.basePath')
);

const features = server.plugins.xpack_main.getFeatures();
return toggleUICapabilities(features, uiCapabilities, activeSpace);
} catch (e) {
return uiCapabilities;
}
});
server.expose('getSpaceId', (request: any) => spacesService.getSpaceId(request));
server.expose('getScopedSpacesClient', spacesService.scopedClient);
},
});
Loading