Give user the option to log out if they encounter a 403 (#75538)

This commit is contained in:
Thomas Watson 2020-10-06 20:40:28 +02:00 committed by GitHub
parent 4c65b6dda4
commit e31ec7eb54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
113 changed files with 863 additions and 445 deletions

View file

@ -121,6 +121,7 @@ The plugin integrates with the core system via lifecycle events: `setup`<!-- -->
| [OnPreAuthToolkit](./kibana-plugin-core-server.onpreauthtoolkit.md) | A tool set defining an outcome of OnPreAuth interceptor for incoming request. |
| [OnPreResponseExtensions](./kibana-plugin-core-server.onpreresponseextensions.md) | Additional data to extend a response. |
| [OnPreResponseInfo](./kibana-plugin-core-server.onpreresponseinfo.md) | Response status code. |
| [OnPreResponseRender](./kibana-plugin-core-server.onpreresponserender.md) | Additional data to extend a response when rendering a new body |
| [OnPreResponseToolkit](./kibana-plugin-core-server.onpreresponsetoolkit.md) | A tool set defining an outcome of OnPreResponse interceptor for incoming request. |
| [OnPreRoutingToolkit](./kibana-plugin-core-server.onpreroutingtoolkit.md) | A tool set defining an outcome of OnPreRouting interceptor for incoming request. |
| [OpsMetrics](./kibana-plugin-core-server.opsmetrics.md) | Regroups metrics gathered by all the collectors. This contains metrics about the os/runtime, the kibana process and the http server. |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [OnPreResponseRender](./kibana-plugin-core-server.onpreresponserender.md) &gt; [body](./kibana-plugin-core-server.onpreresponserender.body.md)
## OnPreResponseRender.body property
the body to use in the response
<b>Signature:</b>
```typescript
body: string;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [OnPreResponseRender](./kibana-plugin-core-server.onpreresponserender.md) &gt; [headers](./kibana-plugin-core-server.onpreresponserender.headers.md)
## OnPreResponseRender.headers property
additional headers to attach to the response
<b>Signature:</b>
```typescript
headers?: ResponseHeaders;
```

View file

@ -0,0 +1,21 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [OnPreResponseRender](./kibana-plugin-core-server.onpreresponserender.md)
## OnPreResponseRender interface
Additional data to extend a response when rendering a new body
<b>Signature:</b>
```typescript
export interface OnPreResponseRender
```
## Properties
| Property | Type | Description |
| --- | --- | --- |
| [body](./kibana-plugin-core-server.onpreresponserender.body.md) | <code>string</code> | the body to use in the response |
| [headers](./kibana-plugin-core-server.onpreresponserender.headers.md) | <code>ResponseHeaders</code> | additional headers to attach to the response |

View file

@ -17,4 +17,5 @@ export interface OnPreResponseToolkit
| Property | Type | Description |
| --- | --- | --- |
| [next](./kibana-plugin-core-server.onpreresponsetoolkit.next.md) | <code>(responseExtensions?: OnPreResponseExtensions) =&gt; OnPreResponseResult</code> | To pass request to the next handler |
| [render](./kibana-plugin-core-server.onpreresponsetoolkit.render.md) | <code>(responseRender: OnPreResponseRender) =&gt; OnPreResponseResult</code> | To override the response with a different body |

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [kibana-plugin-core-server](./kibana-plugin-core-server.md) &gt; [OnPreResponseToolkit](./kibana-plugin-core-server.onpreresponsetoolkit.md) &gt; [render](./kibana-plugin-core-server.onpreresponsetoolkit.render.md)
## OnPreResponseToolkit.render property
To override the response with a different body
<b>Signature:</b>
```typescript
render: (responseRender: OnPreResponseRender) => OnPreResponseResult;
```

View file

@ -175,6 +175,7 @@ type ToolkitMock = jest.Mocked<OnPreResponseToolkit & OnPostAuthToolkit & OnPreR
const createToolkitMock = (): ToolkitMock => {
return {
render: jest.fn(),
next: jest.fn(),
rewriteUrl: jest.fn(),
};

View file

@ -198,6 +198,7 @@ const createAuthToolkitMock = (): jest.Mocked<AuthToolkit> => ({
});
const createOnPreResponseToolkitMock = (): jest.Mocked<OnPreResponseToolkit> => ({
render: jest.fn(),
next: jest.fn(),
});

View file

@ -83,6 +83,7 @@ export { OnPreAuthHandler, OnPreAuthToolkit } from './lifecycle/on_pre_auth';
export {
OnPreResponseHandler,
OnPreResponseToolkit,
OnPreResponseRender,
OnPreResponseExtensions,
OnPreResponseInfo,
} from './lifecycle/on_pre_response';

View file

@ -1286,6 +1286,67 @@ describe('OnPreResponse', () => {
expect(requestBody).toStrictEqual({});
});
it('supports rendering a different response body', async () => {
const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup(
setupDeps
);
const router = createRouter('/');
router.get({ path: '/', validate: false }, (context, req, res) => {
return res.ok({
headers: {
'Original-Header-A': 'A',
},
body: 'original',
});
});
registerOnPreResponse((req, res, t) => {
return t.render({ body: 'overridden' });
});
await server.start();
const result = await supertest(innerServer.listener).get('/').expect(200, 'overridden');
expect(result.header['original-header-a']).toBe('A');
});
it('supports rendering a different response body + headers', async () => {
const { registerOnPreResponse, server: innerServer, createRouter } = await server.setup(
setupDeps
);
const router = createRouter('/');
router.get({ path: '/', validate: false }, (context, req, res) => {
return res.ok({
headers: {
'Original-Header-A': 'A',
'Original-Header-B': 'B',
},
body: 'original',
});
});
registerOnPreResponse((req, res, t) => {
return t.render({
headers: {
'Original-Header-A': 'AA',
'New-Header-C': 'C',
},
body: 'overridden',
});
});
await server.start();
const result = await supertest(innerServer.listener).get('/').expect(200, 'overridden');
expect(result.header['original-header-a']).toBe('AA');
expect(result.header['original-header-b']).toBe('B');
expect(result.header['new-header-c']).toBe('C');
});
});
describe('run interceptors in the right order', () => {

View file

@ -17,16 +17,23 @@
* under the License.
*/
import { Lifecycle, Request, ResponseToolkit as HapiResponseToolkit } from 'hapi';
import { Lifecycle, Request, ResponseObject, ResponseToolkit as HapiResponseToolkit } from 'hapi';
import Boom from 'boom';
import { Logger } from '../../logging';
import { HapiResponseAdapter, KibanaRequest, ResponseHeaders } from '../router';
enum ResultType {
render = 'render',
next = 'next',
}
interface Render {
type: ResultType.render;
body: string;
headers?: ResponseHeaders;
}
interface Next {
type: ResultType.next;
headers?: ResponseHeaders;
@ -35,7 +42,18 @@ interface Next {
/**
* @internal
*/
type OnPreResponseResult = Next;
type OnPreResponseResult = Render | Next;
/**
* Additional data to extend a response when rendering a new body
* @public
*/
export interface OnPreResponseRender {
/** additional headers to attach to the response */
headers?: ResponseHeaders;
/** the body to use in the response */
body: string;
}
/**
* Additional data to extend a response.
@ -55,6 +73,12 @@ export interface OnPreResponseInfo {
}
const preResponseResult = {
render(responseRender: OnPreResponseRender): OnPreResponseResult {
return { type: ResultType.render, body: responseRender.body, headers: responseRender?.headers };
},
isRender(result: OnPreResponseResult): result is Render {
return result && result.type === ResultType.render;
},
next(responseExtensions?: OnPreResponseExtensions): OnPreResponseResult {
return { type: ResultType.next, headers: responseExtensions?.headers };
},
@ -68,11 +92,14 @@ const preResponseResult = {
* @public
*/
export interface OnPreResponseToolkit {
/** To override the response with a different body */
render: (responseRender: OnPreResponseRender) => OnPreResponseResult;
/** To pass request to the next handler */
next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult;
}
const toolkit: OnPreResponseToolkit = {
render: preResponseResult.render,
next: preResponseResult.next,
};
@ -106,26 +133,36 @@ export function adoptToHapiOnPreResponseFormat(fn: OnPreResponseHandler, log: Lo
: response.statusCode;
const result = await fn(KibanaRequest.from(request), { statusCode }, toolkit);
if (!preResponseResult.isNext(result)) {
if (preResponseResult.isNext(result)) {
if (result.headers) {
if (isBoom(response)) {
findHeadersIntersection(response.output.headers, result.headers, log);
// hapi wraps all error response in Boom object internally
response.output.headers = {
...response.output.headers,
...(result.headers as any), // hapi types don't specify string[] as valid value
};
} else {
findHeadersIntersection(response.headers, result.headers, log);
setHeaders(response, result.headers);
}
}
} else if (preResponseResult.isRender(result)) {
const overriddenResponse = responseToolkit.response(result.body).code(statusCode);
const originalHeaders = isBoom(response) ? response.output.headers : response.headers;
setHeaders(overriddenResponse, originalHeaders);
if (result.headers) {
setHeaders(overriddenResponse, result.headers);
}
return overriddenResponse;
} else {
throw new Error(
`Unexpected result from OnPreResponse. Expected OnPreResponseResult, but given: ${result}.`
);
}
if (result.headers) {
if (isBoom(response)) {
findHeadersIntersection(response.output.headers, result.headers, log);
// hapi wraps all error response in Boom object internally
response.output.headers = {
...response.output.headers,
...(result.headers as any), // hapi types don't specify string[] as valid value
};
} else {
findHeadersIntersection(response.headers, result.headers, log);
for (const [headerName, headerValue] of Object.entries(result.headers)) {
response.header(headerName, headerValue as any); // hapi types don't specify string[] as valid value
}
}
}
}
} catch (error) {
log.error(error);
@ -140,6 +177,12 @@ function isBoom(response: any): response is Boom {
return response instanceof Boom;
}
function setHeaders(response: ResponseObject, headers: ResponseHeaders) {
for (const [headerName, headerValue] of Object.entries(headers)) {
response.header(headerName, headerValue as any); // hapi types don't specify string[] as valid value
}
}
// NOTE: responseHeaders contains not a full list of response headers, but only explicitly set on a response object.
// any headers added by hapi internally, like `content-type`, `content-length`, etc. are not present here.
function findHeadersIntersection(

View file

@ -173,6 +173,7 @@ export {
OnPostAuthToolkit,
OnPreResponseHandler,
OnPreResponseToolkit,
OnPreResponseRender,
OnPreResponseExtensions,
OnPreResponseInfo,
RedirectResponseOptions,

View file

@ -1530,9 +1530,16 @@ export interface OnPreResponseInfo {
statusCode: number;
}
// @public
export interface OnPreResponseRender {
body: string;
headers?: ResponseHeaders;
}
// @public
export interface OnPreResponseToolkit {
next: (responseExtensions?: OnPreResponseExtensions) => OnPreResponseResult;
render: (responseRender: OnPreResponseRender) => OnPreResponseResult;
}
// Warning: (ae-forgotten-export) The symbol "OnPreRoutingResult" needs to be exported by the entry point index.d.ts

View file

@ -434,7 +434,7 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
}
}
async getBodyText() {
async getJsonBodyText() {
if (await find.existsByCssSelector('a[id=rawdata-tab]', defaultFindTimeout)) {
// Firefox has 3 tabs and requires navigation to see Raw output
await find.clickByCssSelector('a[id=rawdata-tab]');
@ -449,6 +449,11 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo
}
}
async getBodyText() {
const body = await find.byCssSelector('body');
return await body.getVisibleText();
}
/**
* Helper to detect an OSS licensed Kibana
* Useful for functional testing in cloud environment

View file

@ -26,17 +26,11 @@ export function ErrorPageProvider({ getPageObjects }: FtrProviderContext) {
class ErrorPage {
public async expectForbidden() {
const messageText = await common.getBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 403,
error: 'Forbidden',
message: 'Forbidden',
})
);
expect(messageText).to.contain('You do not have permission to access the requested page');
}
public async expectNotFound() {
const messageText = await common.getBodyText();
const messageText = await common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,

View file

@ -17,10 +17,14 @@ const KIBANA_VERSION_HEADER = 'kbn-version';
*/
export function canRedirectRequest(request: KibanaRequest) {
const headers = request.headers;
const route = request.route;
const hasVersionHeader = headers.hasOwnProperty(KIBANA_VERSION_HEADER);
const hasXsrfHeader = headers.hasOwnProperty(KIBANA_XSRF_HEADER);
const isApiRoute = request.route.options.tags.includes(ROUTE_TAG_API);
const isApiRoute =
route.options.tags.includes(ROUTE_TAG_API) ||
(route.path.startsWith('/api/') && route.path !== '/api/security/logout') ||
route.path.startsWith('/internal/');
const isAjaxRequest = hasVersionHeader || hasXsrfHeader;
return !isApiRoute && !isAjaxRequest;

View file

@ -0,0 +1,3 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ResetSessionPage renders as expected 1`] = `"<html lang=\\"en\\"><head><link href=\\"/some-css-file.css\\" rel=\\"stylesheet\\"/><link href=\\"/some-other-css-file.css\\" rel=\\"stylesheet\\"/>MockedFonts<link rel=\\"apple-touch-icon\\" sizes=\\"180x180\\" href=\\"/path/to/base/ui/favicons/apple-touch-icon.png\\"/><link rel=\\"icon\\" type=\\"image/png\\" sizes=\\"32x32\\" href=\\"/path/to/base/ui/favicons/favicon-32x32.png\\"/><link rel=\\"icon\\" type=\\"image/png\\" sizes=\\"16x16\\" href=\\"/path/to/base/ui/favicons/favicon-16x16.png\\"/><link rel=\\"manifest\\" href=\\"/path/to/base/ui/favicons/manifest.json\\"/><link rel=\\"mask-icon\\" color=\\"#e8488b\\" href=\\"/path/to/base/ui/favicons/safari-pinned-tab.svg\\"/><link rel=\\"shortcut icon\\" href=\\"/path/to/base/ui/favicons/favicon.ico\\"/><script src=\\"/path/to/base/internal/security/reset_session_page.js\\"></script><meta name=\\"msapplication-config\\" content=\\"/path/to/base/ui/favicons/browserconfig.xml\\"/><meta name=\\"theme-color\\" content=\\"#ffffff\\"/></head><body><div class=\\"euiPage\\" style=\\"min-height:100vh\\"><main class=\\"euiPageBody\\"><div class=\\"euiPanel euiPanel--paddingLarge euiPageContent euiPageContent--verticalCenter euiPageContent--horizontalCenter\\"><div class=\\"euiEmptyPrompt\\"><div data-euiicon-type=\\"alert\\" color=\\"danger\\"></div><div class=\\"euiSpacer euiSpacer--s\\"></div><span class=\\"euiTextColor euiTextColor--subdued\\"><h2 class=\\"euiTitle euiTitle--medium\\">You do not have permission to access the requested page</h2><div class=\\"euiSpacer euiSpacer--m\\"></div><div class=\\"euiText euiText--medium\\"><p>Either go back to the previous page or log in as a different user.</p></div></span><div class=\\"euiSpacer euiSpacer--l\\"></div><div class=\\"euiSpacer euiSpacer--s\\"></div><div class=\\"euiFlexGroup euiFlexGroup--gutterMedium euiFlexGroup--alignItemsCenter euiFlexGroup--justifyContentCenter euiFlexGroup--directionColumn euiFlexGroup--responsive\\"><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><a class=\\"euiButton euiButton--primary euiButton--fill\\" href=\\"/path/to/logout\\" rel=\\"noreferrer\\" data-test-subj=\\"ResetSessionButton\\"><span class=\\"euiButtonContent euiButton__content\\"><span class=\\"euiButton__text\\">Log in as different user</span></span></a></div><div class=\\"euiFlexItem euiFlexItem--flexGrowZero\\"><button class=\\"euiButtonEmpty euiButtonEmpty--primary\\" type=\\"button\\" id=\\"goBackButton\\"><span class=\\"euiButtonContent euiButtonEmpty__content\\"><span class=\\"euiButtonEmpty__text\\">Go back</span></span></button></div></div></div></div></main></div></body></html>"`;

View file

@ -100,7 +100,7 @@ describe('initAPIAuthorization', () => {
expect(mockAuthz.mode.useRbacForRequest).toHaveBeenCalledWith(mockRequest);
});
test(`protected route when "mode.useRbacForRequest()" returns true and user isn't authorized responds with a 404`, async () => {
test(`protected route when "mode.useRbacForRequest()" returns true and user isn't authorized responds with a 403`, async () => {
const mockHTTPSetup = coreMock.createSetup().http;
const mockAuthz = authorizationMock.create({ version: '1.0.0-zeta1' });
initAPIAuthorization(mockHTTPSetup, mockAuthz, loggingSystemMock.create().get());
@ -129,7 +129,7 @@ describe('initAPIAuthorization', () => {
await postAuthHandler(mockRequest, mockResponse, mockPostAuthToolkit);
expect(mockResponse.notFound).toHaveBeenCalledTimes(1);
expect(mockResponse.forbidden).toHaveBeenCalledTimes(1);
expect(mockPostAuthToolkit.next).not.toHaveBeenCalled();
expect(mockCheckPrivileges).toHaveBeenCalledWith({
kibana: [mockAuthz.actions.api.get('foo')],

View file

@ -37,7 +37,7 @@ export function initAPIAuthorization(
return toolkit.next();
}
logger.warn(`User not authorized for "${request.url.path}": responding with 404`);
return response.notFound();
logger.warn(`User not authorized for "${request.url.path}": responding with 403`);
return response.forbidden();
});
}

View file

@ -170,7 +170,7 @@ describe('initAppAuthorization', () => {
await postAuthHandler(mockRequest, mockResponse, mockPostAuthToolkit);
expect(mockResponse.notFound).toHaveBeenCalledTimes(1);
expect(mockResponse.forbidden).toHaveBeenCalledTimes(1);
expect(mockPostAuthToolkit.next).not.toHaveBeenCalled();
expect(mockCheckPrivileges).toHaveBeenCalledWith({ kibana: mockAuthz.actions.app.get('foo') });
expect(mockAuthz.mode.useRbacForRequest).toHaveBeenCalledWith(mockRequest);

View file

@ -73,6 +73,6 @@ export function initAppAuthorization(
}
logger.debug(`not authorized for "${appId}"`);
return response.notFound();
return response.forbidden();
});
}

View file

@ -72,6 +72,7 @@ it(`#setup returns exposed services`, () => {
loggers: loggingSystemMock.create(),
kibanaIndexName,
packageVersion: 'some-version',
buildNumber: 42,
features: mockFeaturesSetup,
getSpacesService: mockGetSpacesService,
getCurrentUser: jest.fn(),
@ -130,6 +131,7 @@ describe('#start', () => {
loggers: loggingSystemMock.create(),
kibanaIndexName,
packageVersion: 'some-version',
buildNumber: 42,
features: featuresPluginMock.createSetup(),
getSpacesService: jest
.fn()
@ -201,6 +203,7 @@ it('#stop unsubscribes from license and ES updates.', async () => {
loggers: loggingSystemMock.create(),
kibanaIndexName,
packageVersion: 'some-version',
buildNumber: 42,
features: featuresPluginMock.createSetup(),
getSpacesService: jest
.fn()

View file

@ -4,8 +4,15 @@
* you may not use this file except in compliance with the Elastic License.
*/
import querystring from 'querystring';
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { Subscription, Observable } from 'rxjs';
import * as UiSharedDeps from '@kbn/ui-shared-deps';
import type { Capabilities as UICapabilities } from '../../../../../src/core/types';
import {
LoggerFactory,
KibanaRequest,
@ -43,6 +50,8 @@ import { APPLICATION_PREFIX } from '../../common/constants';
import { SecurityLicense } from '../../common/licensing';
import { CheckPrivilegesWithRequest } from './types';
import { OnlineStatusRetryScheduler } from '../elasticsearch';
import { canRedirectRequest } from '../authentication';
import { ResetSessionPage } from './reset_session_page';
import { AuthenticatedUser } from '..';
export { Actions } from './actions';
@ -51,6 +60,7 @@ export { featurePrivilegeIterator } from './privileges';
interface AuthorizationServiceSetupParams {
packageVersion: string;
buildNumber: number;
http: HttpServiceSetup;
capabilities: CapabilitiesSetup;
clusterClient: ILegacyClusterClient;
@ -89,6 +99,7 @@ export class AuthorizationService {
http,
capabilities,
packageVersion,
buildNumber,
clusterClient,
license,
loggers,
@ -154,6 +165,35 @@ export class AuthorizationService {
initAPIAuthorization(http, authz, loggers.get('api-authorization'));
initAppAuthorization(http, authz, loggers.get('app-authorization'), features);
http.registerOnPreResponse((request, preResponse, toolkit) => {
if (preResponse.statusCode === 403 && canRedirectRequest(request)) {
const basePath = http.basePath.get(request);
const next = `${basePath}${request.url.path}`;
const regularBundlePath = `${basePath}/${buildNumber}/bundles`;
const logoutUrl = http.basePath.prepend(
`/api/security/logout?${querystring.stringify({ next })}`
);
const styleSheetPaths = [
`${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.baseCssDistFilename}`,
`${regularBundlePath}/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`,
`${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`,
`${basePath}/ui/legacy_light_theme.css`,
];
const body = renderToStaticMarkup(
<ResetSessionPage
logoutUrl={logoutUrl}
styleSheetPaths={styleSheetPaths}
basePath={basePath}
/>
);
return toolkit.render({ body, headers: { 'Content-Security-Policy': http.csp.header } });
}
return toolkit.next();
});
return authz;
}

View file

@ -0,0 +1,27 @@
/*
* 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 React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { ResetSessionPage } from './reset_session_page';
jest.mock('../../../../../src/core/server/rendering/views/fonts', () => ({
Fonts: () => <>MockedFonts</>,
}));
describe('ResetSessionPage', () => {
it('renders as expected', async () => {
const body = renderToStaticMarkup(
<ResetSessionPage
logoutUrl="/path/to/logout"
styleSheetPaths={['/some-css-file.css', '/some-other-css-file.css']}
basePath="/path/to/base"
/>
);
expect(body).toMatchSnapshot();
});
});

View file

@ -0,0 +1,128 @@
/*
* 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 React from 'react';
// @ts-expect-error no definitions in component folder
import { EuiButton, EuiButtonEmpty } from '@elastic/eui/lib/components/button';
// @ts-expect-error no definitions in component folder
import { EuiPage, EuiPageBody, EuiPageContent } from '@elastic/eui/lib/components/page';
// @ts-expect-error no definitions in component folder
import { EuiEmptyPrompt } from '@elastic/eui/lib/components/empty_prompt';
// @ts-expect-error no definitions in component folder
import { appendIconComponentCache } from '@elastic/eui/lib/components/icon/icon';
// @ts-expect-error no definitions in component folder
import { icon as EuiIconAlert } from '@elastic/eui/lib/components/icon/assets/alert';
import { FormattedMessage, I18nProvider } from '@kbn/i18n/react';
import { i18n } from '@kbn/i18n';
// eslint-disable-next-line @kbn/eslint/no-restricted-paths
import { Fonts } from '../../../../../src/core/server/rendering/views/fonts';
// Preload the alert icon used by `EuiEmptyPrompt` to ensure that it's loaded
// in advance the first time this page is rendered server-side. If not, the
// icon svg wouldn't contain any paths the first time the page was rendered.
appendIconComponentCache({
alert: EuiIconAlert,
});
export function ResetSessionPage({
logoutUrl,
styleSheetPaths,
basePath,
}: {
logoutUrl: string;
styleSheetPaths: string[];
basePath: string;
}) {
const uiPublicUrl = `${basePath}/ui`;
return (
<html lang={i18n.getLocale()}>
<head>
{styleSheetPaths.map((path) => (
<link href={path} rel="stylesheet" key={path} />
))}
<Fonts url={uiPublicUrl} />
<link
rel="apple-touch-icon"
sizes="180x180"
href={`${uiPublicUrl}/favicons/apple-touch-icon.png`}
/>
<link
rel="icon"
type="image/png"
sizes="32x32"
href={`${uiPublicUrl}/favicons/favicon-32x32.png`}
/>
<link
rel="icon"
type="image/png"
sizes="16x16"
href={`${uiPublicUrl}/favicons/favicon-16x16.png`}
/>
<link rel="manifest" href={`${uiPublicUrl}/favicons/manifest.json`} />
<link
rel="mask-icon"
color="#e8488b"
href={`${uiPublicUrl}/favicons/safari-pinned-tab.svg`}
/>
<link rel="shortcut icon" href={`${uiPublicUrl}/favicons/favicon.ico`} />
<script src={`${basePath}/internal/security/reset_session_page.js`} />
<meta name="msapplication-config" content={`${uiPublicUrl}/favicons/browserconfig.xml`} />
<meta name="theme-color" content="#ffffff" />
</head>
<body>
<I18nProvider>
<EuiPage style={{ minHeight: '100vh' }}>
<EuiPageBody>
<EuiPageContent verticalPosition="center" horizontalPosition="center">
<EuiEmptyPrompt
iconType="alert"
iconColor="danger"
title={
<h2>
<FormattedMessage
id="xpack.security.resetSession.title"
defaultMessage="You do not have permission to access the requested page"
/>
</h2>
}
body={
<p>
<FormattedMessage
id="xpack.security.resetSession.description"
defaultMessage="Either go back to the previous page or log in as a different user."
/>
</p>
}
actions={[
<EuiButton
color="primary"
fill
href={logoutUrl}
data-test-subj="ResetSessionButton"
>
<FormattedMessage
id="xpack.security.resetSession.logOutButtonLabel"
defaultMessage="Log in as different user"
/>
</EuiButton>,
<EuiButtonEmpty id="goBackButton">
<FormattedMessage
id="xpack.security.resetSession.goBackButtonLabel"
defaultMessage="Go back"
/>
</EuiButtonEmpty>,
]}
/>
</EuiPageContent>
</EuiPageBody>
</EuiPage>
</I18nProvider>
</body>
</html>
);
}

View file

@ -206,6 +206,7 @@ export class Plugin {
loggers: this.initializerContext.logger,
kibanaIndexName: legacyConfig.kibana.index,
packageVersion: this.initializerContext.env.packageInfo.version,
buildNumber: this.initializerContext.env.packageInfo.buildNum,
getSpacesService: this.getSpacesService,
features,
getCurrentUser: authc.getCurrentUser,

View file

@ -6,9 +6,11 @@
import { definePrivilegesRoutes } from './privileges';
import { defineRolesRoutes } from './roles';
import { resetSessionPageRoutes } from './reset_session_page';
import { RouteDefinitionParams } from '..';
export function defineAuthorizationRoutes(params: RouteDefinitionParams) {
defineRolesRoutes(params);
definePrivilegesRoutes(params);
resetSessionPageRoutes(params);
}

View file

@ -0,0 +1,28 @@
/*
* 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 { RouteDefinitionParams } from '..';
export function resetSessionPageRoutes({ httpResources }: RouteDefinitionParams) {
httpResources.register(
{
path: '/internal/security/reset_session_page.js',
validate: false,
options: { authRequired: false },
},
(context, request, response) => {
return response.renderJs({
body: `
document.addEventListener('DOMContentLoaded', function(event) {
document.getElementById('goBackButton').onclick = function() {
window.history.back();
}
})
`,
});
}
);
}

View file

@ -83,22 +83,20 @@ export function initSpacesOnPostAuthRequestInterceptor({
const statusCode = wrappedError.statusCode;
// If user is not authorized, or the space cannot be found, allow them to select another space
// by redirecting to the space selector.
const shouldRedirectToSpaceSelector = statusCode === 403 || statusCode === 404;
if (shouldRedirectToSpaceSelector) {
log.debug(
`Unable to navigate to space "${spaceId}", redirecting to Space Selector. ${error}`
);
return response.redirected({
headers: {
location: getSpaceSelectorUrl(serverBasePath),
},
});
} else {
log.error(`Unable to navigate to space "${spaceId}". ${error}`);
return response.customError(wrappedError);
switch (statusCode) {
case 403:
log.debug(`User unauthorized for space "${spaceId}". ${error}`);
return response.forbidden();
case 404:
log.debug(
`Unable to navigate to space "${spaceId}", redirecting to Space Selector. ${error}`
);
return response.redirected({
headers: { location: getSpaceSelectorUrl(serverBasePath) },
});
default:
log.error(`Unable to navigate to space "${spaceId}". ${error}`);
return response.customError(wrappedError);
}
}

View file

@ -158,7 +158,7 @@ export default function securityTests({ getService }: FtrProviderContext) {
.auth(username, password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(404);
.expect(403);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
@ -232,7 +232,7 @@ export default function securityTests({ getService }: FtrProviderContext) {
.auth(user1.username, user1.password)
.set('kbn-xsrf', 'xxx')
.send()
.expect(404);
.expect(403);
});
});
});

View file

@ -49,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) {
});
});
describe('without the "global all" privilege', () => {
it('should return a 404', async () => {
it('should return a 403', async () => {
const username = 'dashboard_all';
const roleName = 'dashboard_all';
const password = `${username}-password`;
@ -76,7 +76,7 @@ export default function ({ getService }: FtrProviderContext) {
.get('/api/features')
.auth(username, password)
.set('kbn-xsrf', 'foo')
.expect(404);
.expect(403);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);

View file

@ -23,11 +23,11 @@ export default function ({ getService }: FtrProviderContext) {
const spaces = getService('spaces');
const clientFactory = getService('infraOpsGraphQLClientFactory');
const expectGraphQL404 = (result: any) => {
const expectGraphQL403 = (result: any) => {
expect(result.response).to.be(undefined);
expect(result.error).not.to.be(undefined);
expect(result.error).to.have.property('networkError');
expect(result.error.networkError).to.have.property('statusCode', 404);
expect(result.error.networkError).to.have.property('statusCode', 403);
};
const expectGraphQLResponse = (result: any) => {
@ -81,7 +81,7 @@ export default function ({ getService }: FtrProviderContext) {
});
const graphQLResult = await executeGraphQLQuery(username, password);
expectGraphQL404(graphQLResult);
expectGraphQL403(graphQLResult);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
@ -156,7 +156,7 @@ export default function ({ getService }: FtrProviderContext) {
});
const graphQLResult = await executeGraphQLQuery(username, password);
expectGraphQL404(graphQLResult);
expectGraphQL403(graphQLResult);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
@ -245,7 +245,7 @@ export default function ({ getService }: FtrProviderContext) {
it(`user_1 can't access APIs in space_3`, async () => {
const graphQLResult = await executeGraphQLQuery(username, password, space3Id);
expectGraphQL404(graphQLResult);
expectGraphQL403(graphQLResult);
});
});
});

View file

@ -79,10 +79,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(annotationRequestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
};

View file

@ -79,10 +79,10 @@ export default ({ getService }: FtrProviderContext) => {
.delete(`/api/ml/annotations/delete/${annotationIdToDelete}`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
await ml.api.waitForAnnotationToExist(annotationIdToDelete);
});

View file

@ -120,10 +120,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
};

View file

@ -124,10 +124,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(annotationUpdateRequestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
const updatedAnnotation = await ml.api.getAnnotationById(originalAnnotation._id);
expect(updatedAnnotation).to.eql(originalAnnotation._source);

View file

@ -87,11 +87,11 @@ export default ({ getService }: FtrProviderContext) => {
model_plot_config: { enabled: true },
},
expected: {
responseCode: 404,
responseCode: 403,
responseBody: {
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
statusCode: 403,
error: 'Forbidden',
message: 'Forbidden',
},
},
},

View file

@ -86,10 +86,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/anomaly_detectors`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
@ -124,10 +124,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/anomaly_detectors/${jobId}_1`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
@ -157,10 +157,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/anomaly_detectors/_stats`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
@ -209,10 +209,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/anomaly_detectors/${jobId}_1/_stats`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
});

View file

@ -60,10 +60,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
await ml.api.waitForCalendarNotToExist(calendarId);
});
@ -73,10 +73,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
await ml.api.waitForCalendarNotToExist(calendarId);
});
});

View file

@ -56,9 +56,9 @@ export default ({ getService }: FtrProviderContext) => {
.delete(`/api/ml/calendars/${calendarId}`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
await ml.api.waitForCalendarToExist(calendarId);
});
@ -67,9 +67,9 @@ export default ({ getService }: FtrProviderContext) => {
.delete(`/api/ml/calendars/${calendarId}`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
await ml.api.waitForCalendarToExist(calendarId);
});

View file

@ -73,8 +73,8 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/calendars`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
expect(body.error).to.eql('Not Found');
.expect(403);
expect(body.error).to.eql('Forbidden');
});
});
@ -126,9 +126,9 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/calendars/${calendarId}`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
});
});

View file

@ -73,13 +73,13 @@ export default ({ getService }: FtrProviderContext) => {
);
});
it('should not allow to update calendar for user without required permission ', async () => {
it('should not allow to update calendar for user without required permission', async () => {
await supertest
.put(`/api/ml/calendars/${calendarId}`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.send(updateCalendarRequestBody)
.expect(404);
.expect(403);
});
it('should not allow to update calendar for unauthorized user', async () => {
@ -88,13 +88,13 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(updateCalendarRequestBody)
.expect(404);
.expect(403);
});
it('should return error if invalid calendarId ', async () => {
it('should return error if invalid calendarId', async () => {
await supertest
.put(`/api/ml/calendars/calendar_id_dne`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.auth(USER.ML_POWERUSER, ml.securityCommon.getPasswordForUser(USER.ML_POWERUSER))
.set(COMMON_REQUEST_HEADERS)
.send(updateCalendarRequestBody)
.expect(404);

View file

@ -92,10 +92,10 @@ export default ({ getService }: FtrProviderContext) => {
.delete(`/api/ml/data_frame/analytics/${analyticsId}`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
await ml.api.waitForDataFrameAnalyticsJobToExist(analyticsId);
});
@ -105,10 +105,10 @@ export default ({ getService }: FtrProviderContext) => {
.delete(`/api/ml/data_frame/analytics/${analyticsId}`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
await ml.api.waitForDataFrameAnalyticsJobToExist(analyticsId);
});

View file

@ -109,10 +109,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/data_frame/analytics`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
@ -147,10 +147,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/data_frame/analytics/${jobId}_1`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
@ -180,10 +180,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/data_frame/analytics/_stats`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
@ -230,9 +230,9 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/data_frame/analytics/${jobId}_1/_stats`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
.expect(403);
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
});

View file

@ -222,10 +222,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
const fetchedJob = await getDFAJob(analyticsId);
// Description should not have changed
@ -243,10 +243,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
const fetchedJob = await getDFAJob(analyticsId);
// Description should not have changed

View file

@ -38,10 +38,10 @@ export default ({ getService }: FtrProviderContext) => {
items: ['104.236.210.185'],
},
expected: {
responseCode: 404,
responseCode: 403,
responseBody: {
error: 'Not Found',
message: 'Not Found',
error: 'Forbidden',
message: 'Forbidden',
},
},
},
@ -54,10 +54,10 @@ export default ({ getService }: FtrProviderContext) => {
items: ['104.236.210.185'],
},
expected: {
responseCode: 404,
responseCode: 403,
responseBody: {
error: 'Not Found',
message: 'Not Found',
error: 'Forbidden',
message: 'Forbidden',
},
},
},

View file

@ -64,9 +64,9 @@ export default ({ getService }: FtrProviderContext) => {
.delete(`/api/ml/filters/${filterId}`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
await ml.api.waitForFilterToExist(filterId);
});
@ -76,9 +76,9 @@ export default ({ getService }: FtrProviderContext) => {
.delete(`/api/ml/filters/${filterId}`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
await ml.api.waitForFilterToExist(filterId);
});

View file

@ -55,9 +55,9 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/filters`)
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
.expect(403);
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
it(`should not allow to retrieve filters for unauthorized user`, async () => {
@ -65,10 +65,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/filters`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
it(`should fetch single filter by id`, async () => {

View file

@ -72,10 +72,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.send(updateFilterRequestBody)
.expect(404);
.expect(403);
// response should return not found
expect(body.error).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
// and the filter should not be updated
const response = await ml.api.getFilter(filterId);
@ -92,9 +92,9 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(updateFilterRequestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
const response = await ml.api.getFilter(filterId);
const updatedFilter = response.body.filters[0];

View file

@ -162,10 +162,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
};

View file

@ -379,10 +379,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_VIEWER, ml.securityCommon.getPasswordForUser(USER.ML_VIEWER))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
};

View file

@ -63,8 +63,8 @@ export default ({ getService }: FtrProviderContext) => {
},
// Note that the jobs and datafeeds are loaded async so the actual error message is not deterministic.
expected: {
responseCode: 404,
error: 'Not Found',
responseCode: 403,
error: 'Forbidden',
},
},
{
@ -75,8 +75,8 @@ export default ({ getService }: FtrProviderContext) => {
},
// Note that the jobs and datafeeds are loaded async so the actual error message is not deterministic.
expected: {
responseCode: 404,
error: 'Not Found',
responseCode: 403,
error: 'Forbidden',
},
},
];

View file

@ -44,8 +44,8 @@ export default ({ getService }: FtrProviderContext) => {
},
// Note that the jobs and datafeeds are loaded async so the actual error message is not deterministic.
expected: {
responseCode: 404,
error: 'Not Found',
responseCode: 403,
error: 'Forbidden',
},
},
{
@ -56,8 +56,8 @@ export default ({ getService }: FtrProviderContext) => {
},
// Note that the jobs and datafeeds are loaded async so the actual error message is not deterministic.
expected: {
responseCode: 404,
error: 'Not Found',
responseCode: 403,
error: 'Forbidden',
},
},
];

View file

@ -58,8 +58,8 @@ export default ({ getService }: FtrProviderContext) => {
jobIds: Object.keys(responseBody),
},
expected: {
responseCode: 404,
error: 'Not Found',
responseCode: 403,
error: 'Forbidden',
},
},
];

View file

@ -160,8 +160,8 @@ export default ({ getService }: FtrProviderContext) => {
requestBody: {},
// Note that the jobs and datafeeds are loaded async so the actual error message is not deterministic.
expected: {
responseCode: 404,
error: 'Not Found',
responseCode: 403,
error: 'Forbidden',
},
},
];

View file

@ -481,9 +481,9 @@ export default ({ getService }: FtrProviderContext) => {
startDatafeed: false,
},
expected: {
responseCode: 404,
error: 'Not Found',
message: 'Not Found',
responseCode: 403,
error: 'Forbidden',
message: 'Forbidden',
},
},
];

View file

@ -123,10 +123,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.send(requestBody)
.expect(404);
.expect(403);
expect(body.error).to.eql('Not Found');
expect(body.message).to.eql('Not Found');
expect(body.error).to.eql('Forbidden');
expect(body.message).to.eql('Forbidden');
});
});
};

View file

@ -96,10 +96,10 @@ export default ({ getService }: FtrProviderContext) => {
.get(`/api/ml/results/${jobId}/categorizer_stats`)
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.be('Not Found');
expect(body.message).to.be('Not Found');
expect(body.error).to.be('Forbidden');
expect(body.message).to.be('Forbidden');
});
it('should fetch all the categorizer stats with per-partition value for job id', async () => {
@ -139,10 +139,10 @@ export default ({ getService }: FtrProviderContext) => {
.query({ partitionByValue: 'sample_web_logs' })
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.be('Not Found');
expect(body.message).to.be('Not Found');
expect(body.error).to.be('Forbidden');
expect(body.message).to.be('Forbidden');
});
});
};

View file

@ -144,10 +144,10 @@ export default ({ getService }: FtrProviderContext) => {
.auth(USER.ML_UNAUTHORIZED, ml.securityCommon.getPasswordForUser(USER.ML_UNAUTHORIZED))
.send({ jobIds: [jobId] })
.set(COMMON_REQUEST_HEADERS)
.expect(404);
.expect(403);
expect(body.error).to.be('Not Found');
expect(body.message).to.be('Not Found');
expect(body.error).to.be('Forbidden');
expect(body.message).to.be('Forbidden');
});
it('should fetch stopped partitions for multiple job ids', async () => {

View file

@ -25,11 +25,11 @@ export default function ({ getService }: FtrProviderContext) {
const spaces = getService('spaces');
const clientFactory = getService('securitySolutionGraphQLClientFactory');
const expectGraphQL404 = (result: any) => {
const expectGraphQL403 = (result: any) => {
expect(result.response).to.be(undefined);
expect(result.error).not.to.be(undefined);
expect(result.error).to.have.property('networkError');
expect(result.error.networkError).to.have.property('statusCode', 404);
expect(result.error.networkError).to.have.property('statusCode', 403);
};
const expectGraphQLResponse = (result: any) => {
@ -101,7 +101,7 @@ export default function ({ getService }: FtrProviderContext) {
});
const graphQLResult = await executeGraphQLQuery(username, password);
expectGraphQL404(graphQLResult);
expectGraphQL403(graphQLResult);
const graphQLIResult = await executeGraphIQLRequest(username, password);
expectGraphIQL404(graphQLIResult);
@ -170,7 +170,7 @@ export default function ({ getService }: FtrProviderContext) {
});
const graphQLResult = await executeGraphQLQuery(username, password);
expectGraphQL404(graphQLResult);
expectGraphQL403(graphQLResult);
const graphQLIResult = await executeGraphIQLRequest(username, password);
expectGraphIQL404(graphQLIResult);
@ -243,7 +243,7 @@ export default function ({ getService }: FtrProviderContext) {
it(`user_1 can't access APIs in space_2`, async () => {
const graphQLResult = await executeGraphQLQuery(username, password, space2Id);
expectGraphQL404(graphQLResult);
expectGraphQL403(graphQLResult);
const graphQLIResult = await executeGraphIQLRequest(username, password, space2Id);
expectGraphIQL404(graphQLIResult);

View file

@ -14,10 +14,10 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
const security = getService('security');
const spaces = getService('spaces');
const expect404 = (result: any) => {
const expect403 = (result: any) => {
expect(result.error).to.be(undefined);
expect(result.response).not.to.be(undefined);
expect(result.response).to.have.property('statusCode', 404);
expect(result.response).to.have.property('statusCode', 403);
};
const expectResponse = (result: any) => {
@ -62,7 +62,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
});
const pingsResult = await executePingsRequest(username, password);
expect404(pingsResult);
expect403(pingsResult);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
@ -137,7 +137,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
});
const pingsResult = await executePingsRequest(username, password);
expect404(pingsResult);
expect403(pingsResult);
} finally {
await security.role.delete(roleName);
await security.user.delete(username);
@ -208,7 +208,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
it(`user_1 can't access APIs in space_2`, async () => {
const pingsResult = await executePingsRequest(username, password);
expect404(pingsResult);
expect403(pingsResult);
});
});
});

View file

@ -18,9 +18,9 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
const start = encodeURIComponent(new Date(Date.now() - 10000).toISOString());
const end = encodeURIComponent(new Date().toISOString());
const expect404 = (result: any) => {
const expect403 = (result: any) => {
expect(result.error).to.be(undefined);
expect(result.response.statusCode).to.be(404);
expect(result.response.statusCode).to.be(403);
};
const expect200 = (result: any) => {
@ -44,93 +44,93 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
req: {
url: `/api/apm/services/foo/errors?start=${start}&end=${end}&uiFilters=%7B%7D&_debug=true`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: { url: `/api/apm/services/foo/errors/bar?start=${start}&end=${end}&uiFilters=%7B%7D` },
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&groupId=bar&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/errors/distribution?start=${start}&end=${end}&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/metrics/charts?start=${start}&end=${end}&agentName=cool-agent&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: { url: `/api/apm/services?start=${start}&end=${end}&uiFilters=%7B%7D` },
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: { url: `/api/apm/services/foo/agent_name?start=${start}&end=${end}` },
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: { url: `/api/apm/services/foo/transaction_types?start=${start}&end=${end}` },
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: { url: `/api/apm/traces?start=${start}&end=${end}&uiFilters=%7B%7D` },
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: { url: `/api/apm/traces/foo?start=${start}&end=${end}` },
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/transaction_groups?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&transactionType=bar&uiFilters=%7B%22environment%22%3A%22testing%22%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&uiFilters=%7B%22environment%22%3A%22testing%22%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/transaction_groups/charts?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%22environment%22%3A%22testing%22%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
req: {
url: `/api/apm/services/foo/transaction_groups/distribution?start=${start}&end=${end}&transactionType=bar&transactionName=baz&uiFilters=%7B%7D`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
{
@ -139,7 +139,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
url: `/api/apm/settings/agent-configuration/search`,
body: { service: { name: 'test-service' }, etag: 'abc' },
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
onExpectationFail: async () => {
const res = await es.search({
@ -153,7 +153,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext)
req: {
url: `/api/apm/settings/custom_links/transaction`,
},
expectForbidden: expect404,
expectForbidden: expect403,
expectResponse: expect200,
},
];

View file

@ -186,7 +186,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte
// ensure that `createConfiguration` throws
expect(true).to.be(false);
} catch (e) {
expect(e.res.statusCode).to.be(404);
expect(e.res.statusCode).to.be(403);
}
});
@ -201,7 +201,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte
// ensure that `updateConfiguration` throws
expect(true).to.be(false);
} catch (e) {
expect(e.res.statusCode).to.be(404);
expect(e.res.statusCode).to.be(403);
}
});
@ -212,7 +212,7 @@ export default function agentConfigurationTests({ getService }: FtrProviderConte
// ensure that `deleteConfiguration` throws
expect(true).to.be(false);
} catch (e) {
expect(e.res.statusCode).to.be(404);
expect(e.res.statusCode).to.be(403);
}
});
});

View file

@ -26,8 +26,8 @@ export default function apiTest({ getService }: FtrProviderContext) {
it('returns an error because the user does not have access', async () => {
const { body } = await getAnomalyDetectionJobs();
expect(body.statusCode).to.be(404);
expect(body.error).to.be('Not Found');
expect(body.statusCode).to.be(403);
expect(body.error).to.be('Forbidden');
});
});
@ -35,8 +35,8 @@ export default function apiTest({ getService }: FtrProviderContext) {
it('returns an error because the user does not have access', async () => {
const { body } = await createAnomalyDetectionJobs(['production', 'staging']);
expect(body.statusCode).to.be(404);
expect(body.error).to.be('Not Found');
expect(body.statusCode).to.be(403);
expect(body.error).to.be('Forbidden');
});
});
});

View file

@ -39,8 +39,8 @@ export default function apiTest({ getService }: FtrProviderContext) {
describe('when calling create endpoint', () => {
it('returns an error because the user does not have access', async () => {
const { body } = await createAnomalyDetectionJobs(['production', 'staging']);
expect(body.statusCode).to.be(404);
expect(body.error).to.be('Not Found');
expect(body.statusCode).to.be(403);
expect(body.error).to.be('Forbidden');
});
});
});

View file

@ -25,16 +25,16 @@ export default function apiTest({ getService }: FtrProviderContext) {
describe('when calling the endpoint for listing jobs', () => {
it('returns an error because the user does not have access', async () => {
const { body } = await getJobs();
expect(body.statusCode).to.be(404);
expect(body.error).to.be('Not Found');
expect(body.statusCode).to.be(403);
expect(body.error).to.be('Forbidden');
});
});
describe('when calling create endpoint', () => {
it('returns an error because the user does not have access', async () => {
const { body } = await createJobs(['production', 'staging']);
expect(body.statusCode).to.be(404);
expect(body.error).to.be('Not Found');
expect(body.statusCode).to.be(403);
expect(body.error).to.be('Forbidden');
});
});
});

View file

@ -35,8 +35,8 @@ export default function apiTest({ getService }: FtrProviderContext) {
it('returns an error because the user does not have access', async () => {
const { body } = await createJobs(['production', 'staging']);
expect(body.statusCode).to.be(404);
expect(body.error).to.be('Not Found');
expect(body.statusCode).to.be(403);
expect(body.error).to.be('Forbidden');
});
});
});

View file

@ -627,7 +627,7 @@ export default function ({ getService }: FtrProviderContext) {
.set('kbn-xsrf', 'xxx')
.auth(KIBANA_ADMIN_USERNAME, KIBANA_ADMIN_PASSWORD)
.send()
.expect(404);
.expect(403);
});
// Since this test re-encrypts objects it should always go last in this suite.

View file

@ -170,12 +170,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks).not.to.contain('APM');
});
it(`renders not found page`, async () => {
it(`renders no permission page`, async () => {
await PageObjects.common.navigateToUrl('apm', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
});
});

View file

@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const security = getService('security');
const PageObjects = getPageObjects(['common', 'canvas', 'security', 'spaceSelector']);
const PageObjects = getPageObjects(['common', 'canvas', 'error', 'security', 'spaceSelector']);
const appsMenu = getService('appsMenu');
const globalNav = getService('globalNav');
@ -217,34 +217,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await security.user.delete('no_canvas_privileges_user');
});
it(`returns a 404`, async () => {
it(`returns a 403`, async () => {
await PageObjects.common.navigateToActualUrl('canvas', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
})
);
PageObjects.error.expectForbidden();
});
it(`create new workpad returns a 404`, async () => {
it(`create new workpad returns a 403`, async () => {
await PageObjects.common.navigateToActualUrl('canvas', 'workpad/create', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
})
);
PageObjects.error.expectForbidden();
});
});
});

View file

@ -107,7 +107,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
@ -127,7 +127,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
}
);
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,

View file

@ -532,7 +532,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks.map((navLink: any) => navLink.text)).to.not.contain(['Dashboard']);
});
it(`landing page shows 404`, async () => {
it(`landing page shows 403`, async () => {
await PageObjects.common.navigateToActualUrl(
'dashboard',
DashboardConstants.LANDING_PAGE_PATH,
@ -541,10 +541,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
}
);
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
it(`create new dashboard shows 404`, async () => {
it(`create new dashboard shows 403`, async () => {
await PageObjects.common.navigateToActualUrl(
'dashboard',
DashboardConstants.CREATE_NEW_DASHBOARD_URL,
@ -553,10 +553,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
}
);
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
it(`edit dashboard for object which doesn't exist shows 404`, async () => {
it(`edit dashboard for object which doesn't exist shows 403`, async () => {
await PageObjects.common.navigateToActualUrl(
'dashboard',
createDashboardEditUrl('i-dont-exist'),
@ -565,10 +565,10 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
}
);
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
it(`edit dashboard for object which exists shows 404`, async () => {
it(`edit dashboard for object which exists shows 403`, async () => {
await PageObjects.common.navigateToActualUrl(
'dashboard',
createDashboardEditUrl('i-exist'),
@ -577,7 +577,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
}
);
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
});
});

View file

@ -228,31 +228,31 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks.map((navLink: any) => navLink.text)).to.not.contain(['Dev Tools']);
});
it(`navigating to console shows 404`, async () => {
it(`navigating to console shows 403`, async () => {
await PageObjects.common.navigateToUrl('console', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
useActualUrl: true,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
it(`navigating to search profiler shows 404`, async () => {
it(`navigating to search profiler shows 403`, async () => {
await PageObjects.common.navigateToUrl('searchProfiler', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
useActualUrl: true,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
it(`navigating to grok debugger shows 404`, async () => {
it(`navigating to grok debugger shows 403`, async () => {
await PageObjects.common.navigateToUrl('grokDebugger', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
useActualUrl: true,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
});
});

View file

@ -412,12 +412,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await security.user.delete('no_discover_privileges_user');
});
it(`shows 404`, async () => {
it(`shows 403`, async () => {
await PageObjects.common.navigateToUrl('discover', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
});
});

View file

@ -182,13 +182,13 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks).not.to.contain('Graph');
});
it(`navigating to app displays a 404`, async () => {
it(`navigating to app displays a 403`, async () => {
await PageObjects.common.navigateToUrl('graph', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
});
});

View file

@ -11,7 +11,7 @@ const DATE_WITH_DATA = DATES.metricsAndLogs.hosts.withData;
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const security = getService('security');
const PageObjects = getPageObjects(['common', 'infraHome', 'security']);
const PageObjects = getPageObjects(['common', 'error', 'infraHome', 'security']);
const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');
const globalNav = getService('globalNav');
@ -423,19 +423,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks).to.not.contain(['Metrics']);
});
it(`metrics app is inaccessible and returns a 404`, async () => {
it(`metrics app is inaccessible and returns a 403`, async () => {
await PageObjects.common.navigateToActualUrl('infraOps', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
})
);
PageObjects.error.expectForbidden();
});
});
});

View file

@ -84,7 +84,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
basePath: '/s/custom_space',
});
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,

View file

@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const security = getService('security');
const PageObjects = getPageObjects(['common', 'infraHome', 'security']);
const PageObjects = getPageObjects(['common', 'error', 'infraHome', 'security']);
const testSubjects = getService('testSubjects');
const appsMenu = getService('appsMenu');
const globalNav = getService('globalNav');
@ -187,19 +187,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks).to.not.contain('Logs');
});
it(`logs app is inaccessible and returns a 404`, async () => {
it(`logs app is inaccessible and returns a 403`, async () => {
await PageObjects.common.navigateToActualUrl('infraLogs', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
})
);
PageObjects.error.expectForbidden();
});
});
});

View file

@ -85,7 +85,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
basePath: '/s/custom_space',
});
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,

View file

@ -9,7 +9,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const security = getService('security');
const PageObjects = getPageObjects(['common', 'settings', 'security', 'maps']);
const PageObjects = getPageObjects(['common', 'error', 'maps', 'settings', 'security']);
const appsMenu = getService('appsMenu');
const testSubjects = getService('testSubjects');
const globalNav = getService('globalNav');
@ -266,19 +266,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks).to.not.contain('Maps');
});
it(`returns a 404`, async () => {
it(`returns a 403`, async () => {
await PageObjects.common.navigateToActualUrl('maps', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
})
);
PageObjects.error.expectForbidden();
});
});
});

View file

@ -84,7 +84,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,

View file

@ -34,7 +34,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
ensureCurrentUrl: false,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
it('should not display the ML file data vis link on the Kibana home page', async () => {

View file

@ -9,7 +9,14 @@ import { FtrProviderContext } from '../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const security = getService('security');
const PageObjects = getPageObjects(['common', 'timelion', 'header', 'security', 'spaceSelector']);
const PageObjects = getPageObjects([
'common',
'error',
'header',
'security',
'spaceSelector',
'timelion',
]);
const appsMenu = getService('appsMenu');
const globalNav = getService('globalNav');
@ -164,19 +171,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await security.user.delete('no_timelion_privileges_user');
});
it(`returns a 404`, async () => {
it(`returns a 403`, async () => {
await PageObjects.common.navigateToActualUrl('timelion', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
error: 'Not Found',
message: 'Not Found',
})
);
PageObjects.error.expectForbidden();
});
});
});

View file

@ -80,7 +80,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
@ -97,7 +97,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,
@ -114,7 +114,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
shouldLoginIfPrompted: false,
});
const messageText = await PageObjects.common.getBodyText();
const messageText = await PageObjects.common.getJsonBodyText();
expect(messageText).to.eql(
JSON.stringify({
statusCode: 404,

View file

@ -173,12 +173,12 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(navLinks).not.to.contain('Uptime');
});
it(`renders not found page`, async () => {
it(`renders no permission page`, async () => {
await PageObjects.common.navigateToUrl('uptime', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
});
});

View file

@ -428,20 +428,20 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await security.user.delete('no_visualize_privileges_user');
});
it(`landing page shows 404`, async () => {
it(`landing page shows 403`, async () => {
await PageObjects.common.navigateToActualUrl('visualize', '', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
it(`edit page shows 404`, async () => {
it(`edit page shows 403`, async () => {
await PageObjects.common.navigateToActualUrl('visualize', '/edit/i-exist', {
ensureCurrentUrl: false,
shouldLoginIfPrompted: false,
});
await PageObjects.error.expectNotFound();
await PageObjects.error.expectForbidden();
});
});
});

View file

@ -88,10 +88,10 @@ export function SecurityPageProvider({ getService, getPageObjects }: FtrProvider
if (await find.existsByCssSelector(rawDataTabLocator)) {
await find.clickByCssSelector(rawDataTabLocator);
}
await PageObjects.error.expectForbidden();
await testSubjects.existOrFail('ResetSessionButton');
});
log.debug(
`Finished login process, found forbidden message. currentUrl = ${await browser.getCurrentUrl()}`
`Finished login process, found reset session button message. currentUrl = ${await browser.getCurrentUrl()}`
);
return;
}

View file

@ -60,12 +60,12 @@ export default function ({ getService }: FtrProviderContext) {
await esArchiver.unload('fleet/agents');
});
it('should return a 404 if user lacks fleet-write permissions', async () => {
it('should return a 403 if user lacks fleet-write permissions', async () => {
const { body: apiResponse } = await supertest
.delete(`/api/fleet/agents/agent1`)
.auth(users.fleet_user.username, users.fleet_user.password)
.set('kbn-xsrf', 'xx')
.expect(404);
.expect(403);
expect(apiResponse).not.to.eql({
action: 'deleted',

View file

@ -93,7 +93,7 @@ export default function ({ getService }: FtrProviderContext) {
await supertest
.get(`/api/fleet/agents`)
.auth(users.kibana_basic_user.username, users.kibana_basic_user.password)
.expect(404);
.expect(403);
});
it('should return a 400 when given an invalid "kuery" value', async () => {
await supertest

View file

@ -69,7 +69,7 @@ const createRequest = ({ type, id, initialNamespaces }: BulkCreateTestCase) => (
});
export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('bulk_create');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_create');
const expectResponseBody = (
testCases: BulkCreateTestCase | BulkCreateTestCase[],
statusCode: 200 | 403,
@ -78,7 +78,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest:
const testCaseArray = Array.isArray(testCases) ? testCases : [testCases];
if (statusCode === 403) {
const types = testCaseArray.map((x) => x.type);
await expectForbidden(types)(response);
await expectSavedObjectForbidden(types)(response);
} else {
// permitted
const savedObjects = response.body.saved_objects;
@ -177,6 +177,6 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest:
return {
addTests,
createTestDefinitions,
expectForbidden,
expectSavedObjectForbidden,
};
}

View file

@ -31,7 +31,7 @@ export const TEST_CASES: Record<string, BulkGetTestCase> = Object.freeze({
});
export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('bulk_get');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_get');
const expectResponseBody = (
testCases: BulkGetTestCase | BulkGetTestCase[],
statusCode: 200 | 403
@ -39,7 +39,7 @@ export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest<an
const testCaseArray = Array.isArray(testCases) ? testCases : [testCases];
if (statusCode === 403) {
const types = testCaseArray.map((x) => x.type);
await expectForbidden(types)(response);
await expectSavedObjectForbidden(types)(response);
} else {
// permitted
const savedObjects = response.body.saved_objects;
@ -113,6 +113,6 @@ export function bulkGetTestSuiteFactory(esArchiver: any, supertest: SuperTest<an
return {
addTests,
createTestDefinitions,
expectForbidden,
expectSavedObjectForbidden,
};
}

View file

@ -36,7 +36,7 @@ const createRequest = ({ type, id, namespace }: BulkUpdateTestCase) => ({
});
export function bulkUpdateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('bulk_update');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_update');
const expectResponseBody = (
testCases: BulkUpdateTestCase | BulkUpdateTestCase[],
statusCode: 200 | 403
@ -44,7 +44,7 @@ export function bulkUpdateTestSuiteFactory(esArchiver: any, supertest: SuperTest
const testCaseArray = Array.isArray(testCases) ? testCases : [testCases];
if (statusCode === 403) {
const types = testCaseArray.map((x) => x.type);
await expectForbidden(types)(response);
await expectSavedObjectForbidden(types)(response);
} else {
// permitted
const savedObjects = response.body.saved_objects;
@ -124,6 +124,6 @@ export function bulkUpdateTestSuiteFactory(esArchiver: any, supertest: SuperTest
return {
addTests,
createTestDefinitions,
expectForbidden,
expectSavedObjectForbidden,
};
}

View file

@ -69,13 +69,13 @@ const createRequest = ({ type, id, initialNamespaces }: CreateTestCase) => ({
});
export function createTestSuiteFactory(es: any, esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('create');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('create');
const expectResponseBody = (
testCase: CreateTestCase,
user?: TestUser
): ExpectResponseBody => async (response: Record<string, any>) => {
if (testCase.failure === 403) {
await expectForbidden(testCase.type)(response);
await expectSavedObjectForbidden(testCase.type)(response);
} else {
// permitted
const object = response.body;

View file

@ -32,12 +32,12 @@ export const TEST_CASES: Record<string, DeleteTestCase> = Object.freeze({
const createRequest = ({ type, id, force }: DeleteTestCase) => ({ type, id, force });
export function deleteTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('delete');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('delete');
const expectResponseBody = (testCase: DeleteTestCase): ExpectResponseBody => async (
response: Record<string, any>
) => {
if (testCase.failure === 403) {
await expectForbidden(testCase.type)(response);
await expectSavedObjectForbidden(testCase.type)(response);
} else {
// permitted
const object = response.body;

View file

@ -115,7 +115,7 @@ const getTestTitle = ({ failure, title }: ExportTestCase) =>
const EMPTY_RESULT = { exportedCount: 0, missingRefCount: 0, missingReferences: [] };
export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) {
const expectForbiddenBulkGet = expectResponses.forbiddenTypes('bulk_get');
const expectSavedObjectForbiddenBulkGet = expectResponses.forbiddenTypes('bulk_get');
const expectResponseBody = (testCase: ExportTestCase): ExpectResponseBody => async (
response: Record<string, any>
) => {
@ -124,7 +124,7 @@ export function exportTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
// In export only, the API uses "bulkGet" or "find" depending on the parameters it receives.
if (failure.statusCode === 403) {
// "bulkGet" was unauthorized, which returns a forbidden error
await expectForbiddenBulkGet(type)(response);
await expectSavedObjectForbiddenBulkGet(type)(response);
} else if (failure.statusCode === 200) {
// "find" was unauthorized, which returns an empty result
expect(response.body).not.to.have.property('error');

View file

@ -24,12 +24,12 @@ const DOES_NOT_EXIST = Object.freeze({ type: 'dashboard', id: 'does-not-exist' }
export const TEST_CASES: Record<string, GetTestCase> = Object.freeze({ ...CASES, DOES_NOT_EXIST });
export function getTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('get');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('get');
const expectResponseBody = (testCase: GetTestCase): ExpectResponseBody => async (
response: Record<string, any>
) => {
if (testCase.failure === 403) {
await expectForbidden(testCase.type)(response);
await expectSavedObjectForbidden(testCase.type)(response);
} else {
// permitted
const object = response.body;

View file

@ -71,7 +71,7 @@ const getConflictDest = (id: string) => ({
});
export function importTestSuiteFactory(es: any, esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('bulk_create');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_create');
const expectResponseBody = (
testCases: ImportTestCase | ImportTestCase[],
statusCode: 200 | 403,
@ -83,7 +83,7 @@ export function importTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
const testCaseArray = Array.isArray(testCases) ? testCases : [testCases];
if (statusCode === 403) {
const types = testCaseArray.map((x) => x.type);
await expectForbidden(types)(response);
await expectSavedObjectForbidden(types)(response);
} else {
// permitted
const { success, successCount, successResults, errors } = response.body;
@ -264,6 +264,6 @@ export function importTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
return {
addTests,
createTestDefinitions,
expectForbidden,
expectSavedObjectForbidden,
};
}

View file

@ -94,7 +94,7 @@ export function resolveImportErrorsTestSuiteFactory(
esArchiver: any,
supertest: SuperTest<any>
) {
const expectForbidden = expectResponses.forbiddenTypes('bulk_create');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('bulk_create');
const expectResponseBody = (
testCases: ResolveImportErrorsTestCase | ResolveImportErrorsTestCase[],
statusCode: 200 | 403,
@ -106,7 +106,7 @@ export function resolveImportErrorsTestSuiteFactory(
const testCaseArray = Array.isArray(testCases) ? testCases : [testCases];
if (statusCode === 403) {
const types = testCaseArray.map((x) => x.type);
await expectForbidden(types)(response);
await expectSavedObjectForbidden(types)(response);
} else {
// permitted
const { success, successCount, successResults, errors } = response.body;
@ -273,6 +273,6 @@ export function resolveImportErrorsTestSuiteFactory(
return {
addTests,
createTestDefinitions,
expectForbidden,
expectSavedObjectForbidden,
};
}

View file

@ -34,12 +34,12 @@ export const TEST_CASES: Record<string, UpdateTestCase> = Object.freeze({
});
export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>) {
const expectForbidden = expectResponses.forbiddenTypes('update');
const expectSavedObjectForbidden = expectResponses.forbiddenTypes('update');
const expectResponseBody = (testCase: UpdateTestCase): ExpectResponseBody => async (
response: Record<string, any>
) => {
if (testCase.failure === 403) {
await expectForbidden(testCase.type)(response);
await expectSavedObjectForbidden(testCase.type)(response);
} else {
// permitted
const object = response.body;

View file

@ -75,11 +75,11 @@ export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const es = getService('legacyEs');
const { addTests, createTestDefinitions, expectForbidden } = bulkCreateTestSuiteFactory(
es,
esArchiver,
supertest
);
const {
addTests,
createTestDefinitions,
expectSavedObjectForbidden,
} = bulkCreateTestSuiteFactory(es, esArchiver, supertest);
const createTests = (overwrite: boolean, spaceId: string, user: TestUser) => {
const { normalTypes, crossNamespace, hiddenType, allTypes } = createTestCases(
overwrite,
@ -97,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) {
spaceId,
user,
singleRequest: true,
responseBodyOverride: expectForbidden(['hiddentype']),
responseBodyOverride: expectSavedObjectForbidden(['hiddentype']),
}),
].flat();
return {

Some files were not shown because too many files have changed in this diff Show more