[Security] tests for API keys app (#48560) (#51217)

This commit is contained in:
Alison Goryachev 2019-11-20 16:02:52 -05:00 committed by GitHub
parent 92a3c970ab
commit b9e2fbf71b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 1057 additions and 2 deletions

View file

@ -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>
`;

View file

@ -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.'
);
});
});
});

View file

@ -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" />

View file

@ -37,6 +37,9 @@ export function serverFixture() {
getUser: stub(),
authenticate: stub(),
deauthenticate: stub(),
authorization: {
application: stub(),
},
},
xpack_main: {

View file

@ -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'
}]
},
},
});
});
});

View file

@ -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'),
}]
},
},
});
});
});

View file

@ -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,
},
},
});
});
});