mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
parent
92a3c970ab
commit
b9e2fbf71b
7 changed files with 1057 additions and 2 deletions
|
@ -0,0 +1,250 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ApiKeysGridPage renders a callout when API keys are not enabled 1`] = `
|
||||
<NotEnabled>
|
||||
<EuiCallOut
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
title={
|
||||
<FormattedMessage
|
||||
defaultMessage="API keys not enabled in Elasticsearch"
|
||||
id="xpack.security.management.apiKeys.table.apiKeysDisabledErrorTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiCallOut euiCallOut--danger"
|
||||
>
|
||||
<div
|
||||
className="euiCallOutHeader"
|
||||
>
|
||||
<EuiIcon
|
||||
aria-hidden="true"
|
||||
className="euiCallOutHeader__icon"
|
||||
size="m"
|
||||
type="alert"
|
||||
>
|
||||
<EuiIconAlert
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoaded euiCallOutHeader__icon"
|
||||
focusable="false"
|
||||
style={null}
|
||||
>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
className="euiIcon euiIcon--medium euiIcon-isLoaded euiCallOutHeader__icon"
|
||||
focusable="false"
|
||||
height={16}
|
||||
style={null}
|
||||
viewBox="0 0 16 16"
|
||||
width={16}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M7.59 10.059L7.35 5.18h1.3L8.4 10.06h-.81zm.394 1.901a.61.61 0 0 1-.448-.186.606.606 0 0 1-.186-.444c0-.174.062-.323.186-.446a.614.614 0 0 1 .448-.184c.169 0 .315.06.44.182.124.122.186.27.186.448a.6.6 0 0 1-.189.446.607.607 0 0 1-.437.184zM2 14a1 1 0 0 1-.878-1.479l6-11a1 1 0 0 1 1.756 0l6 11A1 1 0 0 1 14 14H2zm0-1h12L8 2 2 13z"
|
||||
fillRule="evenodd"
|
||||
/>
|
||||
</svg>
|
||||
</EuiIconAlert>
|
||||
</EuiIcon>
|
||||
<span
|
||||
className="euiCallOutHeader__title"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="API keys not enabled in Elasticsearch"
|
||||
id="xpack.security.management.apiKeys.table.apiKeysDisabledErrorTitle"
|
||||
values={Object {}}
|
||||
>
|
||||
API keys not enabled in Elasticsearch
|
||||
</FormattedMessage>
|
||||
</span>
|
||||
</div>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<div
|
||||
className="euiText euiText--small"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Contact your system administrator and refer to the {link} to enable API keys."
|
||||
id="xpack.security.management.apiKeys.table.apiKeysDisabledErrorDescription"
|
||||
values={
|
||||
Object {
|
||||
"link": <ForwardRef
|
||||
href="undefinedguide/en/elasticsearch/reference/undefined/security-settings.html#api-key-service-settings"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="docs"
|
||||
id="xpack.security.management.apiKeys.table.apiKeysDisabledErrorLinkText"
|
||||
values={Object {}}
|
||||
/>
|
||||
</ForwardRef>,
|
||||
}
|
||||
}
|
||||
>
|
||||
Contact your system administrator and refer to the
|
||||
<EuiLink
|
||||
href="undefinedguide/en/elasticsearch/reference/undefined/security-settings.html#api-key-service-settings"
|
||||
target="_blank"
|
||||
>
|
||||
<a
|
||||
className="euiLink euiLink--primary"
|
||||
href="undefinedguide/en/elasticsearch/reference/undefined/security-settings.html#api-key-service-settings"
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="docs"
|
||||
id="xpack.security.management.apiKeys.table.apiKeysDisabledErrorLinkText"
|
||||
values={Object {}}
|
||||
>
|
||||
docs
|
||||
</FormattedMessage>
|
||||
</a>
|
||||
</EuiLink>
|
||||
to enable API keys.
|
||||
</FormattedMessage>
|
||||
</div>
|
||||
</EuiText>
|
||||
</div>
|
||||
</EuiCallOut>
|
||||
</NotEnabled>
|
||||
`;
|
||||
|
||||
exports[`ApiKeysGridPage renders permission denied if user does not have required permissions 1`] = `
|
||||
<PermissionDenied>
|
||||
<EuiFlexGroup
|
||||
gutterSize="none"
|
||||
>
|
||||
<div
|
||||
className="euiFlexGroup euiFlexGroup--directionRow euiFlexGroup--responsive"
|
||||
>
|
||||
<EuiPageContent
|
||||
horizontalPosition="center"
|
||||
panelPaddingSize="l"
|
||||
>
|
||||
<EuiPanel
|
||||
className="euiPageContent euiPageContent--horizontalCenter"
|
||||
paddingSize="l"
|
||||
>
|
||||
<div
|
||||
className="euiPanel euiPanel--paddingLarge euiPageContent euiPageContent--horizontalCenter"
|
||||
>
|
||||
<EuiEmptyPrompt
|
||||
body={
|
||||
<p
|
||||
data-test-subj="permissionDeniedMessage"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Contact your system administrator."
|
||||
id="xpack.security.management.apiKeys.noPermissionToManageRolesDescription"
|
||||
values={Object {}}
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
iconType="securityApp"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
defaultMessage="You need permission to manage API keys"
|
||||
id="xpack.security.management.apiKeys.deniedPermissionTitle"
|
||||
values={Object {}}
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
>
|
||||
<div
|
||||
className="euiEmptyPrompt"
|
||||
>
|
||||
<EuiIcon
|
||||
color="subdued"
|
||||
size="xxl"
|
||||
type="securityApp"
|
||||
>
|
||||
<EuiIconAppSecurity
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoaded"
|
||||
focusable="false"
|
||||
style={null}
|
||||
>
|
||||
<svg
|
||||
className="euiIcon euiIcon--xxLarge euiIcon--subdued euiIcon--app euiIcon-isLoaded"
|
||||
focusable="false"
|
||||
height={32}
|
||||
style={null}
|
||||
viewBox="0 0 32 32"
|
||||
width={32}
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M14 32l-.36-.14A21.07 21.07 0 0 1 0 12.07V5.44L14 .06l14 5.38v6.63a21.07 21.07 0 0 1-13.64 19.78L14 32zM2 6.82v5.25a19.08 19.08 0 0 0 12 17.77 19.08 19.08 0 0 0 12-17.77V6.82L14 2.2 2 6.82z"
|
||||
/>
|
||||
<path
|
||||
className="euiIcon__fillSecondary"
|
||||
d="M9 17.83h2V23H9zM11 10.18V7H9v3.18a3 3 0 1 0 2 0zM10 14a1 1 0 1 1 0-2 1 1 0 0 1 0 2zM17 7h2v5.17h-2zM21 17a3 3 0 1 0-4 2.82V23h2v-3.18A3 3 0 0 0 21 17zm-3 1a1 1 0 1 1 0-2 1 1 0 0 1 0 2z"
|
||||
/>
|
||||
</svg>
|
||||
</EuiIconAppSecurity>
|
||||
</EuiIcon>
|
||||
<EuiSpacer
|
||||
size="s"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--s"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiTextColor
|
||||
color="subdued"
|
||||
>
|
||||
<span
|
||||
className="euiTextColor euiTextColor--subdued"
|
||||
>
|
||||
<EuiTitle>
|
||||
<h2
|
||||
className="euiTitle euiTitle--medium"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="You need permission to manage API keys"
|
||||
id="xpack.security.management.apiKeys.deniedPermissionTitle"
|
||||
values={Object {}}
|
||||
>
|
||||
You need permission to manage API keys
|
||||
</FormattedMessage>
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
<EuiSpacer
|
||||
size="m"
|
||||
>
|
||||
<div
|
||||
className="euiSpacer euiSpacer--m"
|
||||
/>
|
||||
</EuiSpacer>
|
||||
<EuiText>
|
||||
<div
|
||||
className="euiText euiText--medium"
|
||||
>
|
||||
<p
|
||||
data-test-subj="permissionDeniedMessage"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Contact your system administrator."
|
||||
id="xpack.security.management.apiKeys.noPermissionToManageRolesDescription"
|
||||
values={Object {}}
|
||||
>
|
||||
Contact your system administrator.
|
||||
</FormattedMessage>
|
||||
</p>
|
||||
</div>
|
||||
</EuiText>
|
||||
</span>
|
||||
</EuiTextColor>
|
||||
</div>
|
||||
</EuiEmptyPrompt>
|
||||
</div>
|
||||
</EuiPanel>
|
||||
</EuiPageContent>
|
||||
</div>
|
||||
</EuiFlexGroup>
|
||||
</PermissionDenied>
|
||||
`;
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
let mockSimulate403 = false;
|
||||
let mockSimulate500 = false;
|
||||
let mockAreApiKeysEnabled = true;
|
||||
let mockIsAdmin = true;
|
||||
|
||||
const mock403 = () => ({ body: { statusCode: 403 } });
|
||||
const mock500 = () => ({ body: { error: 'Internal Server Error', message: '', statusCode: 500 } });
|
||||
|
||||
jest.mock('../../../../lib/api_keys_api', () => {
|
||||
return {
|
||||
ApiKeysApi: {
|
||||
async checkPrivileges() {
|
||||
if (mockSimulate403) {
|
||||
throw mock403();
|
||||
}
|
||||
|
||||
return {
|
||||
isAdmin: mockIsAdmin,
|
||||
areApiKeysEnabled: mockAreApiKeysEnabled,
|
||||
};
|
||||
},
|
||||
async getApiKeys() {
|
||||
if (mockSimulate500) {
|
||||
throw mock500();
|
||||
}
|
||||
|
||||
return {
|
||||
apiKeys: [
|
||||
{
|
||||
creation: 1571322182082,
|
||||
expiration: 1571408582082,
|
||||
id: '0QQZ2m0BO2XZwgJFuWTT',
|
||||
invalidated: false,
|
||||
name: 'my-api-key',
|
||||
realm: 'reserved',
|
||||
username: 'elastic',
|
||||
},
|
||||
],
|
||||
};
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
import { mountWithIntl } from 'test_utils/enzyme_helpers';
|
||||
import { ApiKeysGridPage } from './api_keys_grid_page';
|
||||
import React from 'react';
|
||||
import { ReactWrapper } from 'enzyme';
|
||||
import { EuiCallOut } from '@elastic/eui';
|
||||
|
||||
import { NotEnabled } from './not_enabled';
|
||||
import { PermissionDenied } from './permission_denied';
|
||||
|
||||
const waitForRender = async (
|
||||
wrapper: ReactWrapper<any>,
|
||||
condition: (wrapper: ReactWrapper<any>) => boolean
|
||||
) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const interval = setInterval(async () => {
|
||||
await Promise.resolve();
|
||||
wrapper.update();
|
||||
if (condition(wrapper)) {
|
||||
resolve();
|
||||
}
|
||||
}, 10);
|
||||
|
||||
setTimeout(() => {
|
||||
clearInterval(interval);
|
||||
reject(new Error('waitForRender timeout after 2000ms'));
|
||||
}, 2000);
|
||||
});
|
||||
};
|
||||
|
||||
describe('ApiKeysGridPage', () => {
|
||||
beforeEach(() => {
|
||||
mockSimulate403 = false;
|
||||
mockSimulate500 = false;
|
||||
mockAreApiKeysEnabled = true;
|
||||
mockIsAdmin = true;
|
||||
});
|
||||
|
||||
it('renders a loading state when fetching API keys', async () => {
|
||||
const wrapper = mountWithIntl(<ApiKeysGridPage />);
|
||||
|
||||
expect(wrapper.find('[data-test-subj="apiKeysSectionLoading"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('renders a callout when API keys are not enabled', async () => {
|
||||
mockAreApiKeysEnabled = false;
|
||||
const wrapper = mountWithIntl(<ApiKeysGridPage />);
|
||||
|
||||
await waitForRender(wrapper, updatedWrapper => {
|
||||
return updatedWrapper.find(NotEnabled).length > 0;
|
||||
});
|
||||
|
||||
expect(wrapper.find(NotEnabled)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders permission denied if user does not have required permissions', async () => {
|
||||
mockSimulate403 = true;
|
||||
const wrapper = mountWithIntl(<ApiKeysGridPage />);
|
||||
|
||||
await waitForRender(wrapper, updatedWrapper => {
|
||||
return updatedWrapper.find(PermissionDenied).length > 0;
|
||||
});
|
||||
|
||||
expect(wrapper.find(PermissionDenied)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it('renders error callout if error fetching API keys', async () => {
|
||||
mockSimulate500 = true;
|
||||
const wrapper = mountWithIntl(<ApiKeysGridPage />);
|
||||
|
||||
await waitForRender(wrapper, updatedWrapper => {
|
||||
return updatedWrapper.find(EuiCallOut).length > 0;
|
||||
});
|
||||
|
||||
expect(wrapper.find('EuiCallOut[data-test-subj="apiKeysError"]')).toHaveLength(1);
|
||||
});
|
||||
|
||||
describe('Admin view', () => {
|
||||
const wrapper = mountWithIntl(<ApiKeysGridPage />);
|
||||
|
||||
it('renders a callout indicating the user is an administrator', async () => {
|
||||
const calloutEl = 'EuiCallOut[data-test-subj="apiKeyAdminDescriptionCallOut"]';
|
||||
|
||||
await waitForRender(wrapper, updatedWrapper => {
|
||||
return updatedWrapper.find(calloutEl).length > 0;
|
||||
});
|
||||
|
||||
expect(wrapper.find(calloutEl).text()).toEqual('You are an API Key administrator.');
|
||||
});
|
||||
|
||||
it('renders the correct description text', async () => {
|
||||
const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]';
|
||||
|
||||
await waitForRender(wrapper, updatedWrapper => {
|
||||
return updatedWrapper.find(descriptionEl).length > 0;
|
||||
});
|
||||
|
||||
expect(wrapper.find(descriptionEl).text()).toEqual(
|
||||
'View and invalidate API keys. An API key sends requests on behalf of a user.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Non-admin view', () => {
|
||||
mockIsAdmin = false;
|
||||
const wrapper = mountWithIntl(<ApiKeysGridPage />);
|
||||
|
||||
it('does NOT render a callout indicating the user is an administrator', async () => {
|
||||
const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]';
|
||||
const calloutEl = 'EuiCallOut[data-test-subj="apiKeyAdminDescriptionCallOut"]';
|
||||
|
||||
await waitForRender(wrapper, updatedWrapper => {
|
||||
return updatedWrapper.find(descriptionEl).length > 0;
|
||||
});
|
||||
|
||||
expect(wrapper.find(calloutEl).length).toEqual(0);
|
||||
});
|
||||
|
||||
it('renders the correct description text', async () => {
|
||||
const descriptionEl = 'EuiText[data-test-subj="apiKeysDescriptionText"]';
|
||||
|
||||
await waitForRender(wrapper, updatedWrapper => {
|
||||
return updatedWrapper.find(descriptionEl).length > 0;
|
||||
});
|
||||
|
||||
expect(wrapper.find(descriptionEl).text()).toEqual(
|
||||
'View and invalidate your API keys. An API key sends requests on your behalf.'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -86,7 +86,7 @@ export class ApiKeysGridPage extends Component<any, State> {
|
|||
if (isLoadingApp) {
|
||||
return (
|
||||
<EuiPageContent>
|
||||
<SectionLoading>
|
||||
<SectionLoading data-test-subj="apiKeysSectionLoading">
|
||||
<FormattedMessage
|
||||
id="xpack.security.management.apiKeys.table.loadingApiKeysDescription"
|
||||
defaultMessage="Loading API keys…"
|
||||
|
@ -112,6 +112,7 @@ export class ApiKeysGridPage extends Component<any, State> {
|
|||
}
|
||||
color="danger"
|
||||
iconType="alert"
|
||||
data-test-subj="apiKeysError"
|
||||
>
|
||||
{statusCode}: {errorTitle} - {message}
|
||||
</EuiCallOut>
|
||||
|
@ -136,7 +137,7 @@ export class ApiKeysGridPage extends Component<any, State> {
|
|||
}
|
||||
|
||||
const description = (
|
||||
<EuiText color="subdued" size="s">
|
||||
<EuiText color="subdued" size="s" data-test-subj="apiKeysDescriptionText">
|
||||
<p>
|
||||
{isAdmin ? (
|
||||
<FormattedMessage
|
||||
|
@ -309,6 +310,7 @@ export class ApiKeysGridPage extends Component<any, State> {
|
|||
color="success"
|
||||
iconType="user"
|
||||
size="s"
|
||||
data-test-subj="apiKeyAdminDescriptionCallOut"
|
||||
/>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
|
|
|
@ -37,6 +37,9 @@ export function serverFixture() {
|
|||
getUser: stub(),
|
||||
authenticate: stub(),
|
||||
deauthenticate: stub(),
|
||||
authorization: {
|
||||
application: stub(),
|
||||
},
|
||||
},
|
||||
|
||||
xpack_main: {
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
/*
|
||||
* 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 Hapi from 'hapi';
|
||||
import Boom from 'boom';
|
||||
|
||||
import { initGetApiKeysApi } from './get';
|
||||
import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants';
|
||||
|
||||
const createMockServer = () => new Hapi.Server({ debug: false, port: 8080 });
|
||||
|
||||
describe('GET API keys', () => {
|
||||
const getApiKeysTest = (
|
||||
description,
|
||||
{
|
||||
preCheckLicenseImpl = () => null,
|
||||
callWithRequestImpl,
|
||||
asserts,
|
||||
isAdmin = true,
|
||||
}
|
||||
) => {
|
||||
test(description, async () => {
|
||||
const mockServer = createMockServer();
|
||||
const pre = jest.fn().mockImplementation(preCheckLicenseImpl);
|
||||
const mockCallWithRequest = jest.fn();
|
||||
|
||||
if (callWithRequestImpl) {
|
||||
mockCallWithRequest.mockImplementation(callWithRequestImpl);
|
||||
}
|
||||
|
||||
initGetApiKeysApi(mockServer, mockCallWithRequest, pre);
|
||||
|
||||
const headers = {
|
||||
authorization: 'foo',
|
||||
};
|
||||
|
||||
const request = {
|
||||
method: 'GET',
|
||||
url: `${INTERNAL_API_BASE_PATH}/api_key?isAdmin=${isAdmin}`,
|
||||
headers,
|
||||
};
|
||||
|
||||
const { result, statusCode } = await mockServer.inject(request);
|
||||
|
||||
expect(pre).toHaveBeenCalled();
|
||||
|
||||
if (callWithRequestImpl) {
|
||||
expect(mockCallWithRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
authorization: headers.authorization,
|
||||
}),
|
||||
}),
|
||||
'shield.getAPIKeys',
|
||||
{
|
||||
owner: !isAdmin,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
expect(mockCallWithRequest).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
expect(statusCode).toBe(asserts.statusCode);
|
||||
expect(result).toEqual(asserts.result);
|
||||
});
|
||||
};
|
||||
|
||||
describe('failure', () => {
|
||||
getApiKeysTest('returns result of routePreCheckLicense', {
|
||||
preCheckLicenseImpl: () => Boom.forbidden('test forbidden message'),
|
||||
asserts: {
|
||||
statusCode: 403,
|
||||
result: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: 'test forbidden message',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getApiKeysTest('returns error from callWithRequest', {
|
||||
callWithRequestImpl: async () => {
|
||||
throw Boom.notAcceptable('test not acceptable message');
|
||||
},
|
||||
asserts: {
|
||||
statusCode: 406,
|
||||
result: {
|
||||
error: 'Not Acceptable',
|
||||
statusCode: 406,
|
||||
message: 'test not acceptable message',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
getApiKeysTest('returns API keys', {
|
||||
callWithRequestImpl: async () => ({
|
||||
api_keys:
|
||||
[{
|
||||
id: 'YCLV7m0BJ3xI4hhWB648',
|
||||
name: 'test-api-key',
|
||||
creation: 1571670001452,
|
||||
expiration: 1571756401452,
|
||||
invalidated: false,
|
||||
username: 'elastic',
|
||||
realm: 'reserved'
|
||||
}]
|
||||
}),
|
||||
asserts: {
|
||||
statusCode: 200,
|
||||
result: {
|
||||
apiKeys:
|
||||
[{
|
||||
id: 'YCLV7m0BJ3xI4hhWB648',
|
||||
name: 'test-api-key',
|
||||
creation: 1571670001452,
|
||||
expiration: 1571756401452,
|
||||
invalidated: false,
|
||||
username: 'elastic',
|
||||
realm: 'reserved'
|
||||
}]
|
||||
},
|
||||
},
|
||||
});
|
||||
getApiKeysTest('returns only valid API keys', {
|
||||
callWithRequestImpl: async () => ({
|
||||
api_keys:
|
||||
[{
|
||||
id: 'YCLV7m0BJ3xI4hhWB648',
|
||||
name: 'test-api-key1',
|
||||
creation: 1571670001452,
|
||||
expiration: 1571756401452,
|
||||
invalidated: true,
|
||||
username: 'elastic',
|
||||
realm: 'reserved'
|
||||
}, {
|
||||
id: 'YCLV7m0BJ3xI4hhWB648',
|
||||
name: 'test-api-key2',
|
||||
creation: 1571670001452,
|
||||
expiration: 1571756401452,
|
||||
invalidated: false,
|
||||
username: 'elastic',
|
||||
realm: 'reserved'
|
||||
}],
|
||||
}),
|
||||
asserts: {
|
||||
statusCode: 200,
|
||||
result: {
|
||||
apiKeys:
|
||||
[{
|
||||
id: 'YCLV7m0BJ3xI4hhWB648',
|
||||
name: 'test-api-key2',
|
||||
creation: 1571670001452,
|
||||
expiration: 1571756401452,
|
||||
invalidated: false,
|
||||
username: 'elastic',
|
||||
realm: 'reserved'
|
||||
}]
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* 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 Hapi from 'hapi';
|
||||
import Boom from 'boom';
|
||||
|
||||
import { initInvalidateApiKeysApi } from './invalidate';
|
||||
import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants';
|
||||
|
||||
const createMockServer = () => new Hapi.Server({ debug: false, port: 8080 });
|
||||
|
||||
describe('POST invalidate', () => {
|
||||
const postInvalidateTest = (
|
||||
description,
|
||||
{
|
||||
preCheckLicenseImpl = () => null,
|
||||
callWithRequestImpls = [],
|
||||
asserts,
|
||||
payload,
|
||||
}
|
||||
) => {
|
||||
test(description, async () => {
|
||||
const mockServer = createMockServer();
|
||||
const pre = jest.fn().mockImplementation(preCheckLicenseImpl);
|
||||
const mockCallWithRequest = jest.fn();
|
||||
|
||||
for (const impl of callWithRequestImpls) {
|
||||
mockCallWithRequest.mockImplementationOnce(impl);
|
||||
}
|
||||
|
||||
initInvalidateApiKeysApi(mockServer, mockCallWithRequest, pre);
|
||||
|
||||
const headers = {
|
||||
authorization: 'foo',
|
||||
};
|
||||
|
||||
const request = {
|
||||
method: 'POST',
|
||||
url: `${INTERNAL_API_BASE_PATH}/api_key/invalidate`,
|
||||
headers,
|
||||
payload,
|
||||
};
|
||||
|
||||
const { result, statusCode } = await mockServer.inject(request);
|
||||
|
||||
expect(pre).toHaveBeenCalled();
|
||||
|
||||
if (asserts.callWithRequests) {
|
||||
for (const args of asserts.callWithRequests) {
|
||||
expect(mockCallWithRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
authorization: headers.authorization,
|
||||
}),
|
||||
}),
|
||||
...args
|
||||
);
|
||||
}
|
||||
} else {
|
||||
expect(mockCallWithRequest).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
expect(statusCode).toBe(asserts.statusCode);
|
||||
expect(result).toEqual(asserts.result);
|
||||
});
|
||||
};
|
||||
|
||||
describe('failure', () => {
|
||||
postInvalidateTest('returns result of routePreCheckLicense', {
|
||||
preCheckLicenseImpl: () => Boom.forbidden('test forbidden message'),
|
||||
payload: {
|
||||
apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }],
|
||||
isAdmin: true
|
||||
},
|
||||
asserts: {
|
||||
statusCode: 403,
|
||||
result: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: 'test forbidden message',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
postInvalidateTest('returns errors array from callWithRequest', {
|
||||
callWithRequestImpls: [async () => {
|
||||
throw Boom.notAcceptable('test not acceptable message');
|
||||
}],
|
||||
payload: {
|
||||
apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }],
|
||||
isAdmin: true
|
||||
},
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.invalidateAPIKey', {
|
||||
body: {
|
||||
id: 'si8If24B1bKsmSLTAhJV',
|
||||
},
|
||||
}],
|
||||
],
|
||||
statusCode: 200,
|
||||
result: {
|
||||
itemsInvalidated: [],
|
||||
errors: [{
|
||||
id: 'si8If24B1bKsmSLTAhJV',
|
||||
name: 'my-api-key',
|
||||
error: Boom.notAcceptable('test not acceptable message'),
|
||||
}]
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
postInvalidateTest('invalidates API keys', {
|
||||
callWithRequestImpls: [async () => null],
|
||||
payload: {
|
||||
apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }],
|
||||
isAdmin: true
|
||||
},
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.invalidateAPIKey', {
|
||||
body: {
|
||||
id: 'si8If24B1bKsmSLTAhJV',
|
||||
},
|
||||
}],
|
||||
],
|
||||
statusCode: 200,
|
||||
result: {
|
||||
itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }],
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
postInvalidateTest('adds "owner" to body if isAdmin=false', {
|
||||
callWithRequestImpls: [async () => null],
|
||||
payload: {
|
||||
apiKeys: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key', }],
|
||||
isAdmin: false
|
||||
},
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.invalidateAPIKey', {
|
||||
body: {
|
||||
id: 'si8If24B1bKsmSLTAhJV',
|
||||
owner: true,
|
||||
},
|
||||
}],
|
||||
],
|
||||
statusCode: 200,
|
||||
result: {
|
||||
itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key' }],
|
||||
errors: [],
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
postInvalidateTest('returns only successful invalidation requests', {
|
||||
callWithRequestImpls: [
|
||||
async () => null,
|
||||
async () => {
|
||||
throw Boom.notAcceptable('test not acceptable message');
|
||||
}],
|
||||
payload: {
|
||||
apiKeys: [
|
||||
{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key1' },
|
||||
{ id: 'ab8If24B1bKsmSLTAhNC', name: 'my-api-key2' }
|
||||
],
|
||||
isAdmin: true
|
||||
},
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.invalidateAPIKey', {
|
||||
body: {
|
||||
id: 'si8If24B1bKsmSLTAhJV',
|
||||
},
|
||||
}],
|
||||
['shield.invalidateAPIKey', {
|
||||
body: {
|
||||
id: 'ab8If24B1bKsmSLTAhNC',
|
||||
},
|
||||
}],
|
||||
],
|
||||
statusCode: 200,
|
||||
result: {
|
||||
itemsInvalidated: [{ id: 'si8If24B1bKsmSLTAhJV', name: 'my-api-key1' }],
|
||||
errors: [{
|
||||
id: 'ab8If24B1bKsmSLTAhNC',
|
||||
name: 'my-api-key2',
|
||||
error: Boom.notAcceptable('test not acceptable message'),
|
||||
}]
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* 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 Hapi from 'hapi';
|
||||
import Boom from 'boom';
|
||||
|
||||
import { initCheckPrivilegesApi } from './privileges';
|
||||
import { INTERNAL_API_BASE_PATH } from '../../../../../common/constants';
|
||||
|
||||
const createMockServer = () => new Hapi.Server({ debug: false, port: 8080 });
|
||||
|
||||
describe('GET privileges', () => {
|
||||
const getPrivilegesTest = (
|
||||
description,
|
||||
{
|
||||
preCheckLicenseImpl = () => null,
|
||||
callWithRequestImpls = [],
|
||||
asserts,
|
||||
}
|
||||
) => {
|
||||
test(description, async () => {
|
||||
const mockServer = createMockServer();
|
||||
const pre = jest.fn().mockImplementation(preCheckLicenseImpl);
|
||||
const mockCallWithRequest = jest.fn();
|
||||
|
||||
for (const impl of callWithRequestImpls) {
|
||||
mockCallWithRequest.mockImplementationOnce(impl);
|
||||
}
|
||||
|
||||
initCheckPrivilegesApi(mockServer, mockCallWithRequest, pre);
|
||||
|
||||
const headers = {
|
||||
authorization: 'foo',
|
||||
};
|
||||
|
||||
const request = {
|
||||
method: 'GET',
|
||||
url: `${INTERNAL_API_BASE_PATH}/api_key/privileges`,
|
||||
headers,
|
||||
};
|
||||
|
||||
const { result, statusCode } = await mockServer.inject(request);
|
||||
|
||||
expect(pre).toHaveBeenCalled();
|
||||
|
||||
if (asserts.callWithRequests) {
|
||||
for (const args of asserts.callWithRequests) {
|
||||
expect(mockCallWithRequest).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
headers: expect.objectContaining({
|
||||
authorization: headers.authorization,
|
||||
}),
|
||||
}),
|
||||
...args
|
||||
);
|
||||
}
|
||||
} else {
|
||||
expect(mockCallWithRequest).not.toHaveBeenCalled();
|
||||
}
|
||||
|
||||
expect(statusCode).toBe(asserts.statusCode);
|
||||
expect(result).toEqual(asserts.result);
|
||||
});
|
||||
};
|
||||
|
||||
describe('failure', () => {
|
||||
getPrivilegesTest('returns result of routePreCheckLicense', {
|
||||
preCheckLicenseImpl: () => Boom.forbidden('test forbidden message'),
|
||||
asserts: {
|
||||
statusCode: 403,
|
||||
result: {
|
||||
error: 'Forbidden',
|
||||
statusCode: 403,
|
||||
message: 'test forbidden message',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getPrivilegesTest('returns error from first callWithRequest', {
|
||||
callWithRequestImpls: [async () => {
|
||||
throw Boom.notAcceptable('test not acceptable message');
|
||||
}, async () => { }],
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.hasPrivileges', {
|
||||
body: {
|
||||
cluster: [
|
||||
'manage_security',
|
||||
'manage_api_key',
|
||||
],
|
||||
},
|
||||
}],
|
||||
['shield.getAPIKeys', { owner: true }],
|
||||
],
|
||||
statusCode: 406,
|
||||
result: {
|
||||
error: 'Not Acceptable',
|
||||
statusCode: 406,
|
||||
message: 'test not acceptable message',
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getPrivilegesTest('returns error from second callWithRequest', {
|
||||
callWithRequestImpls: [async () => { }, async () => {
|
||||
throw Boom.notAcceptable('test not acceptable message');
|
||||
}],
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.hasPrivileges', {
|
||||
body: {
|
||||
cluster: [
|
||||
'manage_security',
|
||||
'manage_api_key',
|
||||
],
|
||||
},
|
||||
}],
|
||||
['shield.getAPIKeys', { owner: true }],
|
||||
],
|
||||
statusCode: 406,
|
||||
result: {
|
||||
error: 'Not Acceptable',
|
||||
statusCode: 406,
|
||||
message: 'test not acceptable message',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('success', () => {
|
||||
getPrivilegesTest('returns areApiKeysEnabled and isAdmin', {
|
||||
callWithRequestImpls: [
|
||||
async () => ({
|
||||
username: 'elastic',
|
||||
has_all_requested: true,
|
||||
cluster: { manage_api_key: true, manage_security: true },
|
||||
index: {},
|
||||
application: {}
|
||||
}),
|
||||
async () => (
|
||||
{
|
||||
api_keys:
|
||||
[{
|
||||
id: 'si8If24B1bKsmSLTAhJV',
|
||||
name: 'my-api-key',
|
||||
creation: 1574089261632,
|
||||
expiration: 1574175661632,
|
||||
invalidated: false,
|
||||
username: 'elastic',
|
||||
realm: 'reserved'
|
||||
}]
|
||||
}
|
||||
),
|
||||
],
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.getAPIKeys', { owner: true }],
|
||||
['shield.hasPrivileges', {
|
||||
body: {
|
||||
cluster: [
|
||||
'manage_security',
|
||||
'manage_api_key',
|
||||
],
|
||||
},
|
||||
}],
|
||||
],
|
||||
statusCode: 200,
|
||||
result: {
|
||||
areApiKeysEnabled: true,
|
||||
isAdmin: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getPrivilegesTest('returns areApiKeysEnabled=false when getAPIKeys error message includes "api keys are not enabled"', {
|
||||
callWithRequestImpls: [
|
||||
async () => ({
|
||||
username: 'elastic',
|
||||
has_all_requested: true,
|
||||
cluster: { manage_api_key: true, manage_security: true },
|
||||
index: {},
|
||||
application: {}
|
||||
}),
|
||||
async () => {
|
||||
throw Boom.unauthorized('api keys are not enabled');
|
||||
},
|
||||
],
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.getAPIKeys', { owner: true }],
|
||||
['shield.hasPrivileges', {
|
||||
body: {
|
||||
cluster: [
|
||||
'manage_security',
|
||||
'manage_api_key',
|
||||
],
|
||||
},
|
||||
}],
|
||||
],
|
||||
statusCode: 200,
|
||||
result: {
|
||||
areApiKeysEnabled: false,
|
||||
isAdmin: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
getPrivilegesTest('returns isAdmin=false when user has insufficient privileges', {
|
||||
callWithRequestImpls: [
|
||||
async () => ({
|
||||
username: 'elastic',
|
||||
has_all_requested: true,
|
||||
cluster: { manage_api_key: false, manage_security: false },
|
||||
index: {},
|
||||
application: {}
|
||||
}),
|
||||
async () => (
|
||||
{
|
||||
api_keys:
|
||||
[{
|
||||
id: 'si8If24B1bKsmSLTAhJV',
|
||||
name: 'my-api-key',
|
||||
creation: 1574089261632,
|
||||
expiration: 1574175661632,
|
||||
invalidated: false,
|
||||
username: 'elastic',
|
||||
realm: 'reserved'
|
||||
}]
|
||||
}
|
||||
),
|
||||
],
|
||||
asserts: {
|
||||
callWithRequests: [
|
||||
['shield.getAPIKeys', { owner: true }],
|
||||
['shield.hasPrivileges', {
|
||||
body: {
|
||||
cluster: [
|
||||
'manage_security',
|
||||
'manage_api_key',
|
||||
],
|
||||
},
|
||||
}],
|
||||
],
|
||||
statusCode: 200,
|
||||
result: {
|
||||
areApiKeysEnabled: true,
|
||||
isAdmin: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Add table
Add a link
Reference in a new issue