Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
*/

import type { Payload } from '@hapi/boom';
import Boom from '@hapi/boom';
import { isNotFoundFromUnsupportedServer } from '@kbn/core-elasticsearch-server-internal';
import type {
DecoratedError,
Expand All @@ -32,6 +31,7 @@ import {
left,
right,
rawDocExistsInNamespaces,
isForbiddenSpacesError,
} from './utils';
import type { ApiExecutionContext } from './types';

Expand Down Expand Up @@ -69,7 +69,7 @@ export const performBulkGet = async <T>(
availableSpacesPromise = spacesExtension!
.getSearchableNamespaces([ALL_NAMESPACES_STRING])
.catch((err) => {
if (Boom.isBoom(err) && err.output.payload.statusCode === 403) {
if (isForbiddenSpacesError(err)) {
// the user doesn't have access to any spaces; return the current space ID and allow the SOR authZ check to fail
return [SavedObjectsUtils.namespaceIdToString(namespace)];
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import Boom from '@hapi/boom';
import type { estypes } from '@elastic/elasticsearch';
import { isSupportedEsServer } from '@kbn/core-elasticsearch-server-internal';
import type {
Expand Down Expand Up @@ -36,6 +35,7 @@ import {
validateAndConvertAggregations,
} from '../search';
import { includedFields } from '../utils';
import { isForbiddenSpacesError } from './utils';

export interface PerformFindParams {
options: SavedObjectsFindOptions;
Expand Down Expand Up @@ -144,7 +144,7 @@ export const performFind = async <T = unknown, A = unknown>(
try {
namespaces = await spacesExtension.getSearchableNamespaces(options.namespaces);
} catch (err) {
if (Boom.isBoom(err) && err.output.payload.statusCode === 403) {
if (isForbiddenSpacesError(err)) {
// The user is not authorized to access any space, return an empty response.
return SavedObjectsUtils.createEmptyFindResponse<T, A>(options);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/

import Boom from '@hapi/boom';
import { isSupportedEsServer } from '@kbn/core-elasticsearch-server-internal';
import { SavedObjectsErrorHelpers } from '@kbn/core-saved-objects-server';
import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';
Expand All @@ -17,6 +16,7 @@ import type {
SavedObjectsOpenPointInTimeResponse,
} from '@kbn/core-saved-objects-api-server';
import type { ApiExecutionContext } from './types';
import { isForbiddenSpacesError } from './utils';

export interface PerforOpenPointInTimeParams {
type: string | string[];
Expand Down Expand Up @@ -52,7 +52,7 @@ export const performOpenPointInTime = async <T>(
try {
namespaces = await spacesExtension.getSearchableNamespaces(options.namespaces);
} catch (err) {
if (Boom.isBoom(err) && err.output.payload.statusCode === 403) {
if (isForbiddenSpacesError(err)) {
// The user is not authorized to access any space, throw a bad request error.
throw SavedObjectsErrorHelpers.createBadRequestError();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
* License v3.0 only", or the "Server Side Public License, v 1".
*/
import { castArray } from 'lodash';
import Boom from '@hapi/boom';
import type { estypes } from '@elastic/elasticsearch';
import { isSupportedEsServer } from '@kbn/core-elasticsearch-server-internal';
import {
Expand All @@ -23,6 +22,7 @@ import type {
import type { ApiExecutionContext } from './types';
import { getNamespacesBoolFilter } from '../search';
import type { NamespacesBoolFilter } from '../search/search_dsl/query_params';
import { isForbiddenSpacesError } from './utils';

export interface PerformSearchParams {
options: SavedObjectsSearchOptions;
Expand Down Expand Up @@ -73,7 +73,7 @@ export async function performSearch<T extends SavedObjectsRawDocSource, A = unkn
namespaces =
(await spacesExtension?.getSearchableNamespaces(requestedNamespaces)) ?? requestedNamespaces;
} catch (error) {
if (Boom.isBoom(error) && error.output.payload.statusCode === 403) {
if (isForbiddenSpacesError(error)) {
// The user is not authorized to access any space, return an empty response.
return createEmptySearchResponse();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export {
setManaged,
normalizeNamespace,
getSavedObjectNamespaces,
isForbiddenSpacesError,
type GetSavedObjectFromSourceOptions,
} from './internal_utils';
export { type Left, type Either, type Right, isLeft, isRight, left, right } from './either';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import type { estypes } from '@elastic/elasticsearch';
import type { Payload } from '@hapi/boom';
import Boom from '@hapi/boom';
import {
SavedObjectsErrorHelpers,
type ISavedObjectTypeRegistry,
Expand Down Expand Up @@ -294,3 +295,7 @@ export const errorContent = (error: DecoratedError) => error.output.payload;
export function isMgetDoc(doc?: estypes.MgetResponseItem<unknown>): doc is estypes.GetGetResult {
return Boolean(doc && 'found' in doc);
}

export function isForbiddenSpacesError(error: unknown): boolean {
Copy link
Contributor Author

@TinaHeiligers TinaHeiligers Oct 31, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors are usually defined in src/core/packages/saved-objects/server/src/saved_objects_error_helpers.ts and then given a code like CODE_BAD_REQUEST so that we can accurately test against them.

If we only test against the status code I'm afraid that not all errors that pass isForbiddenSpacesError are actually because of a spaces forbidden error.

This might be harder because it's coming from the spacesExtension but I think we should try to move towards errors that communicate the problem in domain language instead of generic 403 status codes.

return Boom.isBoom(error) && error.output.payload.statusCode === 403;
}