Skip to content

Commit 42348d4

Browse files
neptunianashokadityakibanamachine
authored
[Data Usage] add error handling and tests for privilege related errors (#203006)
- handling of 2 error cases to error handler - `security_exception` due to lack of privileges. Metering api will respond when one of the following isn't available as a user privilege `monitor,view_index_metadata,manage,all`. - `index_not_found_exception`. Metering api will respond with this when no indices exist for the privileges it has access to or when no indices are found. - api integration tests for data_streams route for the following cases - returns no data streams when there are none it has access to and responds with appropriate message - returns no data streams without necessary privileges and responds with appropriate message - returns data streams when user only has access to a subset of indices with necessary privileges - functional tests for same as above. these remain skipped due to not being able to create data streams picked up by metering api since we implemented filtering out zero storage size data streams, but useful for local testing with some code changes. ### `security_exception` view <img width="1555" alt="Screenshot 2024-12-04 at 1 14 10 PM" src="https://github.com/user-attachments/assets/241a2eb8-1c77-4592-ba18-b971512e712e"> ### `index_not_found_exception` view <img width="1589" alt="Screenshot 2024-12-04 at 1 13 13 PM" src="https://github.com/user-attachments/assets/12b68d66-9178-4957-b014-5765be348694"> --------- Co-authored-by: Ashokaditya <[email protected]> Co-authored-by: kibanamachine <[email protected]>
1 parent ea1c846 commit 42348d4

File tree

12 files changed

+345
-20
lines changed

12 files changed

+345
-20
lines changed

x-pack/plugins/data_usage/server/common/errors.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
* 2.0.
66
*/
77

8-
export class BaseError<MetaType = unknown> extends Error {
8+
export class DataUsageError<MetaType = unknown> extends Error {
99
constructor(message: string, public readonly meta?: MetaType) {
1010
super(message);
1111
// For debugging - capture name of subclasses
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the Elastic License
4+
* 2.0; you may not use this file except in compliance with the Elastic License
5+
* 2.0.
6+
*/
7+
8+
/* eslint-disable max-classes-per-file */
9+
10+
import { DataUsageError } from './common/errors';
11+
12+
export class NotFoundError extends DataUsageError {}
13+
14+
export class AutoOpsError extends DataUsageError {}
15+
16+
export class NoPrivilegeMeteringError extends DataUsageError {
17+
constructor() {
18+
super(
19+
'You do not have the necessary privileges to access data stream statistics. Please contact your administrator.'
20+
);
21+
}
22+
}
23+
24+
export class NoIndicesMeteringError extends DataUsageError {
25+
constructor() {
26+
super(
27+
'No data streams or indices are available for the current user. Ensure that the data streams or indices you are authorized to access have been created and contain data. If you believe this is an error, please contact your administrator.'
28+
);
29+
}
30+
}

x-pack/plugins/data_usage/server/routes/error_handler.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77

88
import type { IKibanaResponse, KibanaResponseFactory, Logger } from '@kbn/core/server';
99
import { CustomHttpRequestError } from '../utils/custom_http_request_error';
10-
import { BaseError } from '../common/errors';
11-
import { AutoOpsError } from '../services/errors';
12-
13-
export class NotFoundError extends BaseError {}
10+
import {
11+
AutoOpsError,
12+
NoPrivilegeMeteringError,
13+
NoIndicesMeteringError,
14+
NotFoundError,
15+
} from '../errors';
1416

1517
/**
1618
* Default Data Usage Routes error handler
@@ -43,6 +45,14 @@ export const errorHandler = <E extends Error>(
4345
return res.notFound({ body: error });
4446
}
4547

48+
if (error instanceof NoPrivilegeMeteringError) {
49+
return res.forbidden({ body: error });
50+
}
51+
52+
if (error instanceof NoIndicesMeteringError) {
53+
return res.notFound({ body: error });
54+
}
55+
4656
// Kibana CORE will take care of `500` errors when the handler `throw`'s, including logging the error
4757
throw error;
4858
};

x-pack/plugins/data_usage/server/routes/internal/data_streams.test.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { DATA_USAGE_DATA_STREAMS_API_ROUTE } from '../../../common';
1919
import { createMockedDataUsageContext } from '../../mocks';
2020
import { getMeteringStats } from '../../utils/get_metering_stats';
2121
import { CustomHttpRequestError } from '../../utils';
22+
import { NoIndicesMeteringError, NoPrivilegeMeteringError } from '../../errors';
2223

2324
jest.mock('../../utils/get_metering_stats');
2425
const mockGetMeteringStats = getMeteringStats as jest.Mock;
@@ -126,7 +127,7 @@ describe('registerDataStreamsRoute', () => {
126127
});
127128
});
128129

129-
it('should return correct error if metering stats request fails', async () => {
130+
it('should return correct error if metering stats request fails with an unknown error', async () => {
130131
// using custom error for test here to avoid having to import the actual error class
131132
mockGetMeteringStats.mockRejectedValue(
132133
new CustomHttpRequestError('Error getting metring stats!')
@@ -144,6 +145,38 @@ describe('registerDataStreamsRoute', () => {
144145
});
145146
});
146147

148+
it('should return `not found` error if metering stats request fails when no indices', async () => {
149+
mockGetMeteringStats.mockRejectedValue(
150+
new Error(JSON.stringify({ message: 'index_not_found_exception' }))
151+
);
152+
const mockRequest = httpServerMock.createKibanaRequest({ body: {} });
153+
const mockResponse = httpServerMock.createResponseFactory();
154+
const mockRouter = mockCore.http.createRouter.mock.results[0].value;
155+
const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls;
156+
await handler(context, mockRequest, mockResponse);
157+
158+
expect(mockResponse.notFound).toHaveBeenCalledTimes(1);
159+
expect(mockResponse.notFound).toHaveBeenCalledWith({
160+
body: new NoIndicesMeteringError(),
161+
});
162+
});
163+
164+
it('should return `forbidden` error if metering stats request fails with privileges error', async () => {
165+
mockGetMeteringStats.mockRejectedValue(
166+
new Error(JSON.stringify({ message: 'security_exception' }))
167+
);
168+
const mockRequest = httpServerMock.createKibanaRequest({ body: {} });
169+
const mockResponse = httpServerMock.createResponseFactory();
170+
const mockRouter = mockCore.http.createRouter.mock.results[0].value;
171+
const [[, handler]] = mockRouter.versioned.get.mock.results[0].value.addVersion.mock.calls;
172+
await handler(context, mockRequest, mockResponse);
173+
174+
expect(mockResponse.forbidden).toHaveBeenCalledTimes(1);
175+
expect(mockResponse.forbidden).toHaveBeenCalledWith({
176+
body: new NoPrivilegeMeteringError(),
177+
});
178+
});
179+
147180
it.each([
148181
['no datastreams', {}, []],
149182
['empty array', { datastreams: [] }, []],

x-pack/plugins/data_usage/server/routes/internal/data_streams_handler.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { DataUsageContext, DataUsageRequestHandlerContext } from '../../types';
1010
import { errorHandler } from '../error_handler';
1111
import { getMeteringStats } from '../../utils/get_metering_stats';
1212
import { DataStreamsRequestQuery } from '../../../common/rest_types/data_streams';
13+
import { NoIndicesMeteringError, NoPrivilegeMeteringError } from '../../errors';
1314

1415
export const getDataStreamsHandler = (
1516
dataUsageContext: DataUsageContext
@@ -45,6 +46,12 @@ export const getDataStreamsHandler = (
4546
body,
4647
});
4748
} catch (error) {
49+
if (error.message.includes('security_exception')) {
50+
return errorHandler(logger, response, new NoPrivilegeMeteringError());
51+
} else if (error.message.includes('index_not_found_exception')) {
52+
return errorHandler(logger, response, new NoIndicesMeteringError());
53+
}
54+
4855
return errorHandler(logger, response, error);
4956
}
5057
};

x-pack/plugins/data_usage/server/routes/internal/usage_metrics.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import type {
1818
import { DATA_USAGE_METRICS_API_ROUTE } from '../../../common';
1919
import { createMockedDataUsageContext } from '../../mocks';
2020
import { CustomHttpRequestError } from '../../utils';
21-
import { AutoOpsError } from '../../services/errors';
21+
import { AutoOpsError } from '../../errors';
2222
import { transformToUTCtime } from '../../../common/utils';
2323

2424
const timeRange = {

x-pack/plugins/data_usage/server/services/autoops_api.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
type UsageMetricsRequestBody,
2222
} from '../../common/rest_types';
2323
import { AutoOpsConfig } from '../types';
24-
import { AutoOpsError } from './errors';
24+
import { AutoOpsError } from '../errors';
2525
import { appContextService } from './app_context';
2626

2727
const AUTO_OPS_REQUEST_FAILED_PREFIX = '[AutoOps API] Request failed';

x-pack/plugins/data_usage/server/services/errors.ts

Lines changed: 0 additions & 10 deletions
This file was deleted.

x-pack/plugins/data_usage/server/services/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import { ValidationError } from '@kbn/config-schema';
88
import { Logger } from '@kbn/logging';
99
import type { MetricTypes } from '../../common/rest_types';
10-
import { AutoOpsError } from './errors';
10+
import { AutoOpsError } from '../errors';
1111
import { AutoOpsAPIService } from './autoops_api';
1212

1313
export class DataUsageService {

x-pack/test_serverless/api_integration/test_suites/common/data_usage/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export default function ({ loadTestFile }: FtrProviderContext) {
1111
describe('Serverless Data Usage APIs', function () {
1212
this.tags(['esGate']);
1313

14+
loadTestFile(require.resolve('./tests/data_streams_privileges'));
1415
loadTestFile(require.resolve('./tests/data_streams'));
1516
loadTestFile(require.resolve('./tests/metrics'));
1617
});

0 commit comments

Comments
 (0)