diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.test.ts b/x-pack/plugins/security/server/routes/indices/get_fields.test.ts deleted file mode 100644 index 4c6182e99431d..0000000000000 --- a/x-pack/plugins/security/server/routes/indices/get_fields.test.ts +++ /dev/null @@ -1,58 +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; - * you may not use this file except in compliance with the Elastic License. - */ - -import { httpServerMock, elasticsearchServiceMock } from '../../../../../../src/core/server/mocks'; -import { kibanaResponseFactory } from '../../../../../../src/core/server'; - -import { routeDefinitionParamsMock } from '../index.mock'; -import { defineGetFieldsRoutes } from './get_fields'; - -const createFieldMapping = (field: string, type: string) => ({ - [field]: { mapping: { [field]: { type } } }, -}); - -const createEmptyFieldMapping = (field: string) => ({ [field]: { mapping: {} } }); - -const mockFieldMappingResponse = { - foo: { - mappings: { - ...createFieldMapping('fooField', 'keyword'), - ...createFieldMapping('commonField', 'keyword'), - ...createEmptyFieldMapping('emptyField'), - }, - }, - bar: { - mappings: { - ...createFieldMapping('commonField', 'keyword'), - ...createFieldMapping('barField', 'keyword'), - ...createFieldMapping('runtimeField', 'runtime'), - }, - }, -}; - -describe('GET /internal/security/fields/{query}', () => { - it('returns a list of deduplicated fields, omitting empty and runtime fields', async () => { - const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); - - const scopedClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - scopedClient.callAsCurrentUser.mockResolvedValue(mockFieldMappingResponse); - mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(scopedClient); - - defineGetFieldsRoutes(mockRouteDefinitionParams); - - const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; - - const headers = { authorization: 'foo' }; - const mockRequest = httpServerMock.createKibanaRequest({ - method: 'get', - path: `/internal/security/fields/foo`, - headers, - }); - const response = await handler({} as any, mockRequest, kibanaResponseFactory); - expect(response.status).toBe(200); - expect(response.payload).toEqual(['fooField', 'commonField', 'barField']); - }); -}); diff --git a/x-pack/plugins/security/server/routes/indices/get_fields.ts b/x-pack/plugins/security/server/routes/indices/get_fields.ts index 44b8804ed8d6e..356b78aa33879 100644 --- a/x-pack/plugins/security/server/routes/indices/get_fields.ts +++ b/x-pack/plugins/security/server/routes/indices/get_fields.ts @@ -8,20 +8,6 @@ import { schema } from '@kbn/config-schema'; import { RouteDefinitionParams } from '../index'; import { wrapIntoCustomErrorResponse } from '../../errors'; -interface FieldMappingResponse { - [indexName: string]: { - mappings: { - [fieldName: string]: { - mapping: { - [fieldName: string]: { - type: string; - }; - }; - }; - }; - }; -} - export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinitionParams) { router.get( { @@ -37,35 +23,21 @@ export function defineGetFieldsRoutes({ router, clusterClient }: RouteDefinition fields: '*', allowNoIndices: false, includeDefaults: true, - })) as FieldMappingResponse; + })) as Record }>; // The flow is the following (see response format at https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-field-mapping.html): // 1. Iterate over all matched indices. // 2. Extract all the field names from the `mappings` field of the particular index. - // 3. Collect and flatten the list of the field names, omitting any fields without mappings, and any runtime fields + // 3. Collect and flatten the list of the field names. // 4. Use `Set` to get only unique field names. - const fields = Array.from( - new Set( - Object.values(indexMappings).flatMap((indexMapping) => { - return Object.keys(indexMapping.mappings).filter((fieldName) => { - const mappingValues = Object.values(indexMapping.mappings[fieldName].mapping); - const hasMapping = mappingValues.length > 0; - - const isRuntimeField = hasMapping && mappingValues[0]?.type === 'runtime'; - - // fields without mappings are internal fields such as `_routing` and `_index`, - // and therefore don't make sense as autocomplete suggestions for FLS. - - // Runtime fields are not securable via FLS. - // Administrators should instead secure access to the fields which derive this information. - return hasMapping && !isRuntimeField; - }); - }) - ) - ); - return response.ok({ - body: fields, + body: Array.from( + new Set( + Object.values(indexMappings) + .map((indexMapping) => Object.keys(indexMapping.mappings)) + .flat() + ) + ), }); } catch (error) { return response.customError(wrapIntoCustomErrorResponse(error)); diff --git a/x-pack/test/api_integration/apis/security/index_fields.ts b/x-pack/test/api_integration/apis/security/index_fields.ts index 193d0eea1590e..795da7dbe8835 100644 --- a/x-pack/test/api_integration/apis/security/index_fields.ts +++ b/x-pack/test/api_integration/apis/security/index_fields.ts @@ -7,33 +7,10 @@ import expect from '@kbn/expect/expect.js'; import { FtrProviderContext } from '../../ftr_provider_context'; -interface FLSFieldMappingResponse { - flstest: { - mappings: { - [fieldName: string]: { - mapping: { - [fieldName: string]: { - type: string; - }; - }; - }; - }; - }; -} - export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const es = getService('legacyEs'); describe('Index Fields', () => { - before(async () => { - await esArchiver.load('security/flstest/data'); - }); - after(async () => { - await esArchiver.unload('security/flstest/data'); - }); - describe('GET /internal/security/fields/{query}', () => { it('should return a list of available index mapping fields', async () => { await supertest @@ -53,41 +30,6 @@ export default function ({ getService }: FtrProviderContext) { sampleOfExpectedFields.forEach((field) => expect(response.body).to.contain(field)); }); }); - - it('should not include runtime fields', async () => { - // First, make sure the mapping actually includes a runtime field - const fieldMapping = (await es.indices.getFieldMapping({ - index: 'flstest', - fields: '*', - includeDefaults: true, - })) as FLSFieldMappingResponse; - - expect(Object.keys(fieldMapping.flstest.mappings)).to.contain('runtime_customer_ssn'); - expect( - fieldMapping.flstest.mappings.runtime_customer_ssn.mapping.runtime_customer_ssn.type - ).to.eql('runtime'); - - // Now, make sure it's not returned here - const { body: actualFields } = (await supertest - .get('/internal/security/fields/flstest') - .set('kbn-xsrf', 'xxx') - .send() - .expect(200)) as { body: string[] }; - - const expectedFields = [ - 'customer_ssn', - 'customer_ssn.keyword', - 'customer_region', - 'customer_region.keyword', - 'customer_name', - 'customer_name.keyword', - ]; - - actualFields.sort(); - expectedFields.sort(); - - expect(actualFields).to.eql(expectedFields); - }); }); }); } diff --git a/x-pack/test/functional/es_archives/security/flstest/data/mappings.json b/x-pack/test/functional/es_archives/security/flstest/data/mappings.json index 3605533618a93..c6f11ea26f647 100644 --- a/x-pack/test/functional/es_archives/security/flstest/data/mappings.json +++ b/x-pack/test/functional/es_archives/security/flstest/data/mappings.json @@ -30,13 +30,6 @@ } }, "type": "text" - }, - "runtime_customer_ssn": { - "type": "runtime", - "runtime_type": "keyword", - "script": { - "source": "emit(doc['customer_ssn'].value + ' calculated at runtime')" - } } } },