diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index d61cbb909bae..fa127b7462c7 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -3777,24 +3777,16 @@ exports[`Header handles visibility and lock changes 1`] = ` className="euiBreadcrumbs euiHeaderBreadcrumbs euiBreadcrumbs--truncate" data-test-subj="breadcrumbs" > -
-
+ - - - test - - -
-
+ test + + @@ -9475,24 +9467,16 @@ exports[`Header renders condensed header 1`] = ` className="euiBreadcrumbs euiHeaderBreadcrumbs euiBreadcrumbs--truncate" data-test-subj="breadcrumbs" > -
-
+ - - - test - - -
-
+ test + + diff --git a/src/core/public/saved_objects/saved_objects_client.ts b/src/core/public/saved_objects/saved_objects_client.ts index 2f389b851de2..f1f3b87c6dc7 100644 --- a/src/core/public/saved_objects/saved_objects_client.ts +++ b/src/core/public/saved_objects/saved_objects_client.ts @@ -373,7 +373,6 @@ export class SavedObjectsClient { namespaces: 'namespaces', preference: 'preference', workspaces: 'workspaces', - queryDSL: 'queryDSL', }; const currentWorkspaceId = this._getCurrentWorkspace(); @@ -384,14 +383,17 @@ export class SavedObjectsClient { finalWorkspaces = Array.from(new Set([currentWorkspaceId])); } - const renamedQuery = renameKeys(renameMap, { - ...options, - ...(finalWorkspaces - ? { - workspaces: finalWorkspaces, - } - : {}), - }); + const renamedQuery = renameKeys, any>( + renameMap, + { + ...options, + ...(finalWorkspaces + ? { + workspaces: finalWorkspaces, + } + : {}), + } + ); const query = pick.apply(null, [renamedQuery, ...Object.values(renameMap)]) as Partial< Record >; diff --git a/src/core/server/saved_objects/permission_control/client.ts b/src/core/server/saved_objects/permission_control/client.ts index 11e3d6e58dc8..f7c0c800ec4c 100644 --- a/src/core/server/saved_objects/permission_control/client.ts +++ b/src/core/server/saved_objects/permission_control/client.ts @@ -6,9 +6,10 @@ import { i18n } from '@osd/i18n'; import { OpenSearchDashboardsRequest } from '../../http'; import { ensureRawRequest } from '../../http/router'; import { SavedObjectsServiceStart } from '../saved_objects_service'; -import { SavedObjectsBulkGetObject, SavedObjectsRepository, SavedObjectsUtils } from '../service'; +import { SavedObjectsBulkGetObject } from '../service'; import { ACL, Principals, TransformedPermission, PrincipalType } from './acl'; import { Logger } from '../../logging'; +import { WORKSPACE_TYPE } from '../../../utils'; export type SavedObjectsPermissionControlContract = Pick< SavedObjectsPermissionControl, @@ -163,11 +164,19 @@ export class SavedObjectsPermissionControl { permissionModes: SavedObjectsPermissionModes ) { const principals = this.getPrincipalsFromRequest(request); - const repository = this.getInternalRepository() as SavedObjectsRepository; - return await SavedObjectsUtils.getPermittedWorkspaceIds({ - principals, - repository, - permissionModes, - }); + const repository = this.getInternalRepository(); + try { + const result = await repository?.find({ + type: [WORKSPACE_TYPE], + ACLSearchParams: { + permissionModes, + principals, + }, + perPage: 999, + }); + return result?.saved_objects.map((item) => item.id); + } catch (e) { + return []; + } } } diff --git a/src/core/server/saved_objects/service/lib/repository.mock.ts b/src/core/server/saved_objects/service/lib/repository.mock.ts index 4c535b18b5f2..e02d4dd0b638 100644 --- a/src/core/server/saved_objects/service/lib/repository.mock.ts +++ b/src/core/server/saved_objects/service/lib/repository.mock.ts @@ -45,8 +45,6 @@ const create = (): jest.Mocked => ({ deleteByNamespace: jest.fn(), incrementCounter: jest.fn(), addToWorkspaces: jest.fn(), - getPermissionQuery: jest.fn(), - processFindOptions: jest.fn(), deleteByWorkspace: jest.fn(), deleteFromWorkspaces: jest.fn(), }); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index e3575d575fdc..be7c093b59c9 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -31,7 +31,6 @@ import { omit, intersection } from 'lodash'; import type { opensearchtypes } from '@opensearch-project/opensearch'; import uuid from 'uuid'; -import { i18n } from '@osd/i18n'; import type { ISavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { DeleteDocumentResponse, OpenSearchClient } from '../../../opensearch/'; @@ -91,9 +90,7 @@ import { FIND_DEFAULT_PER_PAGE, SavedObjectsUtils, } from './utils'; -import { PUBLIC_WORKSPACE_ID, WorkspacePermissionMode } from '../../../../utils/constants'; -import { ACL, Principals } from '../../permission_control/acl'; -import { WORKSPACE_TYPE } from '../../../../utils'; +import { PUBLIC_WORKSPACE_ID } from '../../../../utils/constants'; // BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository // so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient. @@ -902,7 +899,7 @@ export class SavedObjectsRepository { filter, preference, workspaces, - queryDSL, + ACLSearchParams, } = options; if (!type && !typeToNamespacesMap) { @@ -977,7 +974,7 @@ export class SavedObjectsRepository { hasReference, kueryNode, workspaces, - queryDSL, + ACLSearchParams, }), }, }; @@ -1934,111 +1931,6 @@ export class SavedObjectsRepository { }; } - async getPermissionQuery(props: { - permissionTypes: string[]; - principals: Principals; - savedObjectType?: string[]; - }) { - return ACL.generateGetPermittedSavedObjectsQueryDSL( - props.permissionTypes, - props.principals, - props.savedObjectType - ); - } - - async processFindOptions(props: { - options: SavedObjectsFindOptions & { permissionModes?: string[] }; - principals: Principals; - }): Promise { - const { principals } = props; - const options = { ...props.options }; - if (this.isRelatedToWorkspace(options.type)) { - options.queryDSL = await this.getPermissionQuery({ - permissionTypes: options.permissionModes ?? [ - WorkspacePermissionMode.LibraryRead, - WorkspacePermissionMode.LibraryWrite, - WorkspacePermissionMode.Management, - ], - principals, - savedObjectType: [WORKSPACE_TYPE], - }); - } else { - const permittedWorkspaceIds = await SavedObjectsUtils.getPermittedWorkspaceIds({ - permissionModes: [ - WorkspacePermissionMode.LibraryRead, - WorkspacePermissionMode.LibraryWrite, - WorkspacePermissionMode.Management, - ], - principals, - repository: this, - }); - - if (options.workspaces) { - const permittedWorkspaces = options.workspaces.filter((item) => - (permittedWorkspaceIds || []).includes(item) - ); - if (!permittedWorkspaces.length) { - /** - * If user does not have any one workspace access - * deny the request - */ - throw SavedObjectsErrorHelpers.decorateNotAuthorizedError( - new Error( - i18n.translate('workspace.permission.invalidate', { - defaultMessage: 'Invalid workspace permission', - }) - ) - ); - } - - /** - * Overwrite the options.workspaces when user has access on partial workspaces. - */ - options.workspaces = permittedWorkspaces; - } else { - const queryDSL = await this.getPermissionQuery({ - permissionTypes: [WorkspacePermissionMode.Read, WorkspacePermissionMode.Write], - principals, - savedObjectType: Array.isArray(options.type) ? options.type : [options.type], - }); - options.workspaces = undefined; - /** - * Select all the docs that - * 1. ACL matches read or write permission OR - * 2. workspaces matches library_read or library_write or management OR - * 3. Advanced settings - */ - options.queryDSL = { - query: { - bool: { - filter: [ - { - bool: { - should: [ - { - term: { - type: 'config', - }, - }, - queryDSL.query, - { - terms: { - workspaces: permittedWorkspaceIds, - }, - }, - ], - }, - }, - ], - }, - }, - }; - } - } - - return options; - } - /** * Returns index specified by the given type or the default index * @@ -2167,16 +2059,6 @@ export class SavedObjectsRepository { } return body; } - - /** - * check if the type include workspace - * Workspace permission check is totally different from object permission check. - * @param type - * @returns - */ - private isRelatedToWorkspace(type: string | string[]): boolean { - return type === WORKSPACE_TYPE || (Array.isArray(type) && type.includes(WORKSPACE_TYPE)); - } } function getBulkOperationError(error: { type: string; reason?: string }, type: string, id: string) { diff --git a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts index d9fbf7199c18..96da76df5a68 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/query_params.ts @@ -27,13 +27,14 @@ * specific language governing permissions and limitations * under the License. */ -import { mergeWith, isArray } from 'lodash'; // @ts-expect-error no ts import { opensearchKuery } from '../../../opensearch_query'; type KueryNode = any; import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; import { ALL_NAMESPACES_STRING, DEFAULT_NAMESPACE_STRING } from '../utils'; +import { SavedObjectsFindOptions } from '../../../types'; +import { ACL } from '../../../permission_control/acl'; /** * Gets the types based on the type. Uses mappings to support @@ -166,7 +167,7 @@ interface QueryParams { hasReference?: HasReferenceQueryParams; kueryNode?: KueryNode; workspaces?: string[]; - queryDSL?: Record; + ACLSearchParams?: SavedObjectsFindOptions['ACLSearchParams']; } export function getClauseForReference(reference: HasReferenceQueryParams) { @@ -224,7 +225,7 @@ export function getQueryParams({ hasReference, kueryNode, workspaces, - queryDSL, + ACLSearchParams, }: QueryParams) { const types = getTypes( registry, @@ -283,12 +284,43 @@ export function getQueryParams({ const result = { query: { bool } }; - if (queryDSL) { - return mergeWith({}, result, queryDSL, (objValue, srcValue) => { - if (isArray(objValue)) { - return objValue.concat(srcValue); - } - }); + if (ACLSearchParams) { + const shouldClause: any = []; + if (ACLSearchParams.permissionModes && ACLSearchParams.principals) { + const permissionDSL = ACL.generateGetPermittedSavedObjectsQueryDSL( + ACLSearchParams.permissionModes, + ACLSearchParams.principals + ); + shouldClause.push(permissionDSL.query); + } + + if (ACLSearchParams.workspaces) { + shouldClause.push({ + terms: { + workspaces: ACLSearchParams.workspaces, + }, + }); + } + + if (shouldClause.length) { + bool.filter.push({ + bool: { + should: [ + /** + * TODO remove this clause once advanced settings has attached with permission + */ + { + term: { + type: 'config', + }, + }, + ...shouldClause, + ], + }, + }); + } + + return result; } return result; } diff --git a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts index d6b8b83ac87e..a93e134a9757 100644 --- a/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts +++ b/src/core/server/saved_objects/service/lib/search_dsl/search_dsl.ts @@ -34,6 +34,7 @@ import { IndexMapping } from '../../../mappings'; import { getQueryParams } from './query_params'; import { getSortingParams } from './sorting_params'; import { ISavedObjectTypeRegistry } from '../../../saved_objects_type_registry'; +import { SavedObjectsFindOptions } from '../../../types'; type KueryNode = any; @@ -53,7 +54,7 @@ interface GetSearchDslOptions { }; kueryNode?: KueryNode; workspaces?: string[]; - queryDSL?: Record; + ACLSearchParams?: SavedObjectsFindOptions['ACLSearchParams']; } export function getSearchDsl( @@ -74,7 +75,7 @@ export function getSearchDsl( hasReference, kueryNode, workspaces, - queryDSL, + ACLSearchParams, } = options; if (!type) { @@ -98,7 +99,7 @@ export function getSearchDsl( hasReference, kueryNode, workspaces, - queryDSL, + ACLSearchParams, }), ...getSortingParams(mappings, type, sortField, sortOrder), }; diff --git a/src/core/server/saved_objects/service/lib/utils.ts b/src/core/server/saved_objects/service/lib/utils.ts index c2a156da60d2..490c2b7083d2 100644 --- a/src/core/server/saved_objects/service/lib/utils.ts +++ b/src/core/server/saved_objects/service/lib/utils.ts @@ -29,10 +29,7 @@ */ import { SavedObjectsFindOptions } from '../../types'; -import { SavedObjectsFindResponse, SavedObjectsRepository } from '..'; -import { Principals } from '../../permission_control/acl'; -import { SavedObjectsPermissionModes } from '../../permission_control/client'; -import { WORKSPACE_TYPE } from '../../../../utils'; +import { SavedObjectsFindResponse } from '..'; export const DEFAULT_NAMESPACE_STRING = 'default'; export const ALL_NAMESPACES_STRING = '*'; @@ -90,27 +87,4 @@ export class SavedObjectsUtils { ): string[] { return targetWorkspaces?.filter((item) => !baseWorkspaces?.includes(item)) || []; } - - public static async getPermittedWorkspaceIds(props: { - principals: Principals; - repository: SavedObjectsRepository; - permissionModes: SavedObjectsPermissionModes; - }) { - const { principals, repository, permissionModes } = props; - const queryDSL = await repository.getPermissionQuery({ - permissionTypes: permissionModes, - principals, - savedObjectType: [WORKSPACE_TYPE], - }); - try { - const result = await repository?.find({ - type: [WORKSPACE_TYPE], - queryDSL, - perPage: 999, - }); - return result?.saved_objects.map((item) => item.id); - } catch (e) { - return []; - } - } } diff --git a/src/core/server/saved_objects/service/saved_objects_client.ts b/src/core/server/saved_objects/service/saved_objects_client.ts index 759d6ed8351a..b6f5abea26ef 100644 --- a/src/core/server/saved_objects/service/saved_objects_client.ts +++ b/src/core/server/saved_objects/service/saved_objects_client.ts @@ -28,8 +28,8 @@ * under the License. */ -import { Permissions, Principals } from '../permission_control/acl'; -import { ISavedObjectsRepository, SavedObjectsRepository } from './lib'; +import { Permissions } from '../permission_control/acl'; +import { ISavedObjectsRepository } from './lib'; import { SavedObject, SavedObjectError, @@ -480,26 +480,6 @@ export class SavedObjectsClient { return await this._repository.addToWorkspaces(objects, workspaces, options); }; - /** - * Different DB may have different query DSL for given params - */ - getPermissionQuery = async ( - props: Parameters[0] - ) => { - return await this._repository.getPermissionQuery(props); - }; - - /** - * Different DB may have different query to find granted objects, - * provide a placeholder here for other query implementation - */ - processFindOptions = async (props: { - options: SavedObjectsFindOptions; - principals: Principals; - }): Promise => { - return await this._repository.processFindOptions(props); - }; - /** * delete saved objects by workspace id * @param workspace diff --git a/src/core/server/saved_objects/types.ts b/src/core/server/saved_objects/types.ts index 25ccbff66dd7..aa2d1baf85de 100644 --- a/src/core/server/saved_objects/types.ts +++ b/src/core/server/saved_objects/types.ts @@ -45,6 +45,7 @@ export { } from './import/types'; import { SavedObject } from '../../types'; +import { Principals } from './permission_control/acl'; type KueryNode = any; @@ -111,7 +112,14 @@ export interface SavedObjectsFindOptions { /** An optional OpenSearch preference value to be used for the query **/ preference?: string; workspaces?: string[]; - queryDSL?: Record; + /** + * The params here will be combined with bool clause and is used for filtering with ACL structure. + */ + ACLSearchParams?: { + workspaces?: string[]; + principals?: Principals; + permissionModes?: string[]; + }; } /** diff --git a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts index d8071301d73d..7da3e7977c8a 100644 --- a/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts +++ b/src/plugins/workspace/server/saved_objects/workspace_saved_objects_client_wrapper.ts @@ -136,6 +136,16 @@ export class WorkspaceSavedObjectsClientWrapper { return matchAny; } + /** + * check if the type include workspace + * Workspace permission check is totally different from object permission check. + * @param type + * @returns + */ + private isRelatedToWorkspace(type: string | string[]): boolean { + return type === WORKSPACE_TYPE || (Array.isArray(type) && type.includes(WORKSPACE_TYPE)); + } + public wrapperFactory: SavedObjectsClientWrapperFactory = (wrapperOptions) => { const deleteWithWorkspacePermissionControl = async ( type: string, @@ -348,11 +358,66 @@ export class WorkspaceSavedObjectsClientWrapper { options: SavedObjectsFindOptions & Pick ) => { const principals = this.permissionControl.getPrincipalsFromRequest(wrapperOptions.request); - const processedOptions = await wrapperOptions.client.processFindOptions({ - options, - principals, - }); - return await wrapperOptions.client.find(processedOptions); + if (!options.ACLSearchParams) { + options.ACLSearchParams = {}; + } + if (this.isRelatedToWorkspace(options.type)) { + options.ACLSearchParams.permissionModes = [ + WorkspacePermissionMode.LibraryRead, + WorkspacePermissionMode.LibraryWrite, + WorkspacePermissionMode.Management, + ]; + options.ACLSearchParams.principals = principals; + } else { + const permittedWorkspaceIds = await this.permissionControl.getPermittedWorkspaceIds( + wrapperOptions.request, + [ + WorkspacePermissionMode.LibraryRead, + WorkspacePermissionMode.LibraryWrite, + WorkspacePermissionMode.Management, + ] + ); + + if (options.workspaces) { + const permittedWorkspaces = options.workspaces.filter((item) => + (permittedWorkspaceIds || []).includes(item) + ); + if (!permittedWorkspaces.length) { + /** + * If user does not have any one workspace access + * deny the request + */ + throw SavedObjectsErrorHelpers.decorateNotAuthorizedError( + new Error( + i18n.translate('workspace.permission.invalidate', { + defaultMessage: 'Invalid workspace permission', + }) + ) + ); + } + + /** + * Overwrite the options.workspaces when user has access on partial workspaces. + */ + options.workspaces = permittedWorkspaces; + } else { + /** + * Select all the docs that + * 1. ACL matches read or write permission OR + * 2. workspaces matches library_read or library_write or management OR + * 3. Advanced settings + */ + options.workspaces = undefined; + options.ACLSearchParams.workspaces = permittedWorkspaceIds; + options.ACLSearchParams.permissionModes = [ + WorkspacePermissionMode.Read, + WorkspacePermissionMode.Write, + ]; + options.ACLSearchParams.principals = principals; + } + } + + return await wrapperOptions.client.find(options); }; const addToWorkspacesWithPermissionControl = async (