Invalidate alert API Key when generating a new one (#53732)

* Initial work to auto cleanup old API keys

* Fix ESLint error

* Rename confusing variables

* Add test to ensure thrown errors are swallowed

* Add more tests

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Mike Côté 2020-01-03 13:40:20 -05:00 committed by GitHub
parent 47830c75d9
commit 8cc778a64a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 352 additions and 48 deletions

View file

@ -12,24 +12,33 @@ import { alertTypeRegistryMock } from './alert_type_registry.mock';
import { TaskStatus } from '../../task_manager/server';
import { IntervalSchedule } from './types';
import { resolvable } from './test_utils';
import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks';
const taskManager = taskManagerMock.create();
const alertTypeRegistry = alertTypeRegistryMock.create();
const savedObjectsClient = savedObjectsClientMock.create();
const encryptedSavedObjects = encryptedSavedObjectsMock.createStart();
const alertsClientParams = {
taskManager,
alertTypeRegistry,
savedObjectsClient,
spaceId: 'default',
namespace: 'default',
getUserName: jest.fn(),
createAPIKey: jest.fn(),
invalidateAPIKey: jest.fn(),
logger: loggingServiceMock.create().get(),
encryptedSavedObjectsPlugin: encryptedSavedObjects,
};
beforeEach(() => {
jest.resetAllMocks();
alertsClientParams.createAPIKey.mockResolvedValue({ created: false });
alertsClientParams.createAPIKey.mockResolvedValue({ apiKeysEnabled: false });
alertsClientParams.invalidateAPIKey.mockResolvedValue({
apiKeysEnabled: true,
result: { error_count: 0 },
});
alertsClientParams.getUserName.mockResolvedValue('elastic');
taskManager.runNow.mockResolvedValue({ id: '' });
});
@ -725,7 +734,7 @@ describe('create()', () => {
async executor() {},
});
alertsClientParams.createAPIKey.mockResolvedValueOnce({
created: true,
apiKeysEnabled: true,
result: { id: '123', api_key: 'abc' },
});
savedObjectsClient.bulkGet.mockResolvedValueOnce({
@ -841,7 +850,7 @@ describe('create()', () => {
describe('enable()', () => {
test('enables an alert', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -900,7 +909,7 @@ describe('enable()', () => {
test(`doesn't enable already enabled alerts`, async () => {
const alertsClient = new AlertsClient(alertsClientParams);
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -918,7 +927,7 @@ describe('enable()', () => {
test('calls the API key function', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -943,7 +952,7 @@ describe('enable()', () => {
ownerId: null,
});
alertsClientParams.createAPIKey.mockResolvedValueOnce({
created: true,
apiKeysEnabled: true,
result: { id: '123', api_key: 'abc' },
});
@ -978,6 +987,41 @@ describe('enable()', () => {
scope: ['alerting'],
});
});
test('swallows error when invalidate API key throws', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail'));
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
schedule: { interval: '10s' },
alertTypeId: '2',
enabled: false,
apiKey: Buffer.from('123:abc').toString('base64'),
},
version: '123',
references: [],
});
taskManager.schedule.mockResolvedValueOnce({
id: 'task-123',
scheduledAt: new Date(),
attempts: 0,
status: TaskStatus.Idle,
runAt: new Date(),
state: {},
params: {},
taskType: '',
startedAt: null,
retryAt: null,
ownerId: null,
});
await alertsClient.enable({ id: '1' });
expect(alertsClientParams.logger.error).toHaveBeenCalledWith(
'Failed to invalidate API Key: Fail'
);
});
});
describe('disable()', () => {
@ -1390,7 +1434,7 @@ describe('find()', () => {
describe('delete()', () => {
test('successfully removes an alert', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -1437,6 +1481,48 @@ describe('delete()', () => {
]
`);
});
test('swallows error when invalidate API key throws', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail'));
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
alertTypeId: '123',
schedule: { interval: '10s' },
params: {
bar: true,
},
apiKey: Buffer.from('123:abc').toString('base64'),
scheduledTaskId: 'task-123',
actions: [
{
group: 'default',
actionRef: 'action_0',
params: {
foo: true,
},
},
],
},
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
],
});
savedObjectsClient.delete.mockResolvedValueOnce({
success: true,
});
await alertsClient.delete({ id: '1' });
expect(alertsClientParams.logger.error).toHaveBeenCalledWith(
'Failed to invalidate API Key: Fail'
);
});
});
describe('update()', () => {
@ -1448,7 +1534,7 @@ describe('update()', () => {
actionGroups: ['default'],
async executor() {},
});
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -1603,7 +1689,7 @@ describe('update()', () => {
actionGroups: ['default'],
async executor() {},
});
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -1786,7 +1872,7 @@ describe('update()', () => {
actionGroups: ['default'],
async executor() {},
});
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -1810,7 +1896,7 @@ describe('update()', () => {
],
});
alertsClientParams.createAPIKey.mockResolvedValueOnce({
created: true,
apiKeysEnabled: true,
result: { id: '123', api_key: 'abc' },
});
savedObjectsClient.update.mockResolvedValueOnce({
@ -1952,7 +2038,7 @@ describe('update()', () => {
},
async executor() {},
});
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -1986,6 +2072,93 @@ describe('update()', () => {
);
});
it('swallows error when invalidate API key throws', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail'));
alertTypeRegistry.get.mockReturnValueOnce({
id: '123',
name: 'Test',
actionGroups: ['default'],
async executor() {},
});
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
enabled: true,
alertTypeId: '123',
scheduledTaskId: 'task-123',
apiKey: Buffer.from('123:abc').toString('base64'),
},
references: [],
version: '123',
});
savedObjectsClient.bulkGet.mockResolvedValueOnce({
saved_objects: [
{
id: '1',
type: 'action',
attributes: {
actionTypeId: 'test',
},
references: [],
},
],
});
savedObjectsClient.update.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
enabled: true,
schedule: { interval: '10s' },
params: {
bar: true,
},
actions: [
{
group: 'default',
actionRef: 'action_0',
actionTypeId: 'test',
params: {
foo: true,
},
},
],
scheduledTaskId: 'task-123',
},
references: [
{
name: 'action_0',
type: 'action',
id: '1',
},
],
});
await alertsClient.update({
id: '1',
data: {
schedule: { interval: '10s' },
name: 'abc',
tags: ['foo'],
params: {
bar: true,
},
actions: [
{
group: 'default',
id: '1',
params: {
foo: true,
},
},
],
},
});
expect(alertsClientParams.logger.error).toHaveBeenCalledWith(
'Failed to invalidate API Key: Fail'
);
});
describe('updating an alert schedule', () => {
function mockApiCalls(
alertId: string,
@ -2012,7 +2185,7 @@ describe('update()', () => {
},
],
});
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: alertId,
type: 'alert',
attributes: {
@ -2212,7 +2385,7 @@ describe('update()', () => {
describe('updateApiKey()', () => {
test('updates the API key for the alert', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
savedObjectsClient.get.mockResolvedValueOnce({
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
@ -2224,7 +2397,7 @@ describe('updateApiKey()', () => {
references: [],
});
alertsClientParams.createAPIKey.mockResolvedValueOnce({
created: true,
apiKeysEnabled: true,
result: { id: '123', api_key: 'abc' },
});
@ -2243,4 +2416,30 @@ describe('updateApiKey()', () => {
{ version: '123' }
);
});
test('swallows error when invalidate API key throws', async () => {
const alertsClient = new AlertsClient(alertsClientParams);
alertsClientParams.invalidateAPIKey.mockRejectedValue(new Error('Fail'));
encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({
id: '1',
type: 'alert',
attributes: {
schedule: { interval: '10s' },
alertTypeId: '2',
enabled: true,
apiKey: Buffer.from('123:abc').toString('base64'),
},
version: '123',
references: [],
});
alertsClientParams.createAPIKey.mockResolvedValueOnce({
apiKeysEnabled: true,
result: { id: '123', api_key: 'abc' },
});
await alertsClient.updateApiKey({ id: '1' });
expect(alertsClientParams.logger.error).toHaveBeenCalledWith(
'Failed to invalidate API Key: Fail'
);
});
});

View file

@ -23,26 +23,32 @@ import {
} from './types';
import { TaskManagerStartContract } from './shim';
import { validateAlertTypeParams } from './lib';
import { CreateAPIKeyResult as SecurityPluginCreateAPIKeyResult } from '../../../../plugins/security/server';
import {
InvalidateAPIKeyParams,
CreateAPIKeyResult as SecurityPluginCreateAPIKeyResult,
InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult,
} from '../../../../plugins/security/server';
import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../plugins/encrypted_saved_objects/server';
interface FailedCreateAPIKeyResult {
created: false;
}
interface SuccessCreateAPIKeyResult {
created: true;
result: SecurityPluginCreateAPIKeyResult;
}
export type CreateAPIKeyResult = FailedCreateAPIKeyResult | SuccessCreateAPIKeyResult;
type NormalizedAlertAction = Omit<AlertAction, 'actionTypeId'>;
export type CreateAPIKeyResult =
| { apiKeysEnabled: false }
| { apiKeysEnabled: true; result: SecurityPluginCreateAPIKeyResult };
export type InvalidateAPIKeyResult =
| { apiKeysEnabled: false }
| { apiKeysEnabled: true; result: SecurityPluginInvalidateAPIKeyResult };
interface ConstructorOptions {
logger: Logger;
taskManager: TaskManagerStartContract;
savedObjectsClient: SavedObjectsClientContract;
alertTypeRegistry: AlertTypeRegistry;
encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract;
spaceId?: string;
namespace?: string;
getUserName: () => Promise<string | null>;
createAPIKey: () => Promise<CreateAPIKeyResult>;
invalidateAPIKey: (params: InvalidateAPIKeyParams) => Promise<InvalidateAPIKeyResult>;
}
export interface FindOptions {
@ -106,10 +112,15 @@ export class AlertsClient {
private readonly logger: Logger;
private readonly getUserName: () => Promise<string | null>;
private readonly spaceId?: string;
private readonly namespace?: string;
private readonly taskManager: TaskManagerStartContract;
private readonly savedObjectsClient: SavedObjectsClientContract;
private readonly alertTypeRegistry: AlertTypeRegistry;
private readonly createAPIKey: () => Promise<CreateAPIKeyResult>;
private readonly invalidateAPIKey: (
params: InvalidateAPIKeyParams
) => Promise<InvalidateAPIKeyResult>;
encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract;
constructor({
alertTypeRegistry,
@ -117,16 +128,22 @@ export class AlertsClient {
taskManager,
logger,
spaceId,
namespace,
getUserName,
createAPIKey,
invalidateAPIKey,
encryptedSavedObjectsPlugin,
}: ConstructorOptions) {
this.logger = logger;
this.getUserName = getUserName;
this.spaceId = spaceId;
this.namespace = namespace;
this.taskManager = taskManager;
this.alertTypeRegistry = alertTypeRegistry;
this.savedObjectsClient = savedObjectsClient;
this.createAPIKey = createAPIKey;
this.invalidateAPIKey = invalidateAPIKey;
this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin;
}
public async create({ data, options }: CreateOptions) {
@ -206,21 +223,26 @@ export class AlertsClient {
}
public async delete({ id }: { id: string }) {
const alertSavedObject = await this.savedObjectsClient.get('alert', id);
const decryptedAlertSavedObject = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser<
RawAlert
>('alert', id, { namespace: this.namespace });
const removeResult = await this.savedObjectsClient.delete('alert', id);
if (alertSavedObject.attributes.scheduledTaskId) {
await this.taskManager.remove(alertSavedObject.attributes.scheduledTaskId);
if (decryptedAlertSavedObject.attributes.scheduledTaskId) {
await this.taskManager.remove(decryptedAlertSavedObject.attributes.scheduledTaskId);
}
await this.invalidateApiKey({ apiKey: decryptedAlertSavedObject.attributes.apiKey });
return removeResult;
}
public async update({ id, data }: UpdateOptions) {
const alert = await this.savedObjectsClient.get<RawAlert>('alert', id);
const updateResult = await this.updateAlert({ id, data }, alert);
const decryptedAlertSavedObject = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser<
RawAlert
>('alert', id, { namespace: this.namespace });
const updateResult = await this.updateAlert({ id, data }, decryptedAlertSavedObject);
if (
updateResult.scheduledTaskId &&
!isEqual(alert.attributes.schedule, updateResult.schedule)
!isEqual(decryptedAlertSavedObject.attributes.schedule, updateResult.schedule)
) {
this.taskManager.runNow(updateResult.scheduledTaskId).catch((err: Error) => {
this.logger.error(
@ -262,6 +284,9 @@ export class AlertsClient {
references,
}
);
await this.invalidateApiKey({ apiKey: attributes.apiKey });
return this.getAlertFromRaw(
id,
updatedObject.attributes,
@ -274,7 +299,7 @@ export class AlertsClient {
apiKey: CreateAPIKeyResult,
username: string | null
): Pick<RawAlert, 'apiKey' | 'apiKeyOwner'> {
return apiKey.created
return apiKey.apiKeysEnabled
? {
apiKeyOwner: username,
apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'),
@ -286,7 +311,12 @@ export class AlertsClient {
}
public async updateApiKey({ id }: { id: string }) {
const { version, attributes } = await this.savedObjectsClient.get('alert', id);
const {
version,
attributes,
} = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser<RawAlert>('alert', id, {
namespace: this.namespace,
});
const username = await this.getUserName();
await this.savedObjectsClient.update(
@ -299,10 +329,36 @@ export class AlertsClient {
},
{ version }
);
await this.invalidateApiKey({ apiKey: attributes.apiKey });
}
private async invalidateApiKey({ apiKey }: { apiKey: string | null }): Promise<void> {
if (!apiKey) {
return;
}
try {
const apiKeyId = Buffer.from(apiKey, 'base64')
.toString()
.split(':')[0];
const response = await this.invalidateAPIKey({ id: apiKeyId });
if (response.apiKeysEnabled === true && response.result.error_count > 0) {
this.logger.error(`Failed to invalidate API Key [id="${apiKeyId}"]`);
}
} catch (e) {
this.logger.error(`Failed to invalidate API Key: ${e.message}`);
}
}
public async enable({ id }: { id: string }) {
const { attributes, version } = await this.savedObjectsClient.get('alert', id);
const {
version,
attributes,
} = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser<RawAlert>('alert', id, {
namespace: this.namespace,
});
if (attributes.enabled === false) {
const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId);
const username = await this.getUserName();
@ -319,6 +375,7 @@ export class AlertsClient {
},
{ version }
);
await this.invalidateApiKey({ apiKey: attributes.apiKey });
}
}

View file

@ -10,6 +10,7 @@ import { alertTypeRegistryMock } from '../alert_type_registry.mock';
import { taskManagerMock } from '../../../task_manager/server/task_manager.mock';
import { KibanaRequest } from '../../../../../../src/core/server';
import { loggingServiceMock } from '../../../../../../src/core/server/mocks';
import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks';
jest.mock('../alerts_client');
@ -25,6 +26,8 @@ const alertsClientFactoryParams: jest.Mocked<ConstructorOpts> = {
taskManager: taskManagerMock.create(),
alertTypeRegistry: alertTypeRegistryMock.create(),
getSpaceId: jest.fn(),
spaceIdToNamespace: jest.fn(),
encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.createStart(),
};
const fakeRequest: Request = {
headers: {},
@ -45,6 +48,7 @@ const fakeRequest: Request = {
beforeEach(() => {
jest.resetAllMocks();
alertsClientFactoryParams.getSpaceId.mockReturnValue('default');
alertsClientFactoryParams.spaceIdToNamespace.mockReturnValue('default');
});
test('creates an alerts client with proper constructor arguments', async () => {
@ -57,8 +61,11 @@ test('creates an alerts client with proper constructor arguments', async () => {
taskManager: alertsClientFactoryParams.taskManager,
alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry,
spaceId: 'default',
namespace: 'default',
getUserName: expect.any(Function),
createAPIKey: expect.any(Function),
invalidateAPIKey: expect.any(Function),
encryptedSavedObjectsPlugin: alertsClientFactoryParams.encryptedSavedObjectsPlugin,
});
});
@ -84,23 +91,23 @@ test('getUserName() returns a name when security is enabled', async () => {
expect(userNameResult).toEqual('bob');
});
test('createAPIKey() returns { created: false } when security is disabled', async () => {
test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled', async () => {
const factory = new AlertsClientFactory(alertsClientFactoryParams);
factory.create(KibanaRequest.from(fakeRequest), fakeRequest);
const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0];
const createAPIKeyResult = await constructorCall.createAPIKey();
expect(createAPIKeyResult).toEqual({ created: false });
expect(createAPIKeyResult).toEqual({ apiKeysEnabled: false });
});
test('createAPIKey() returns { created: false } when security is enabled but ES security is disabled', async () => {
test('createAPIKey() returns { apiKeysEnabled: false } when security is enabled but ES security is disabled', async () => {
const factory = new AlertsClientFactory(alertsClientFactoryParams);
factory.create(KibanaRequest.from(fakeRequest), fakeRequest);
const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0];
securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce(null);
const createAPIKeyResult = await constructorCall.createAPIKey();
expect(createAPIKeyResult).toEqual({ created: false });
expect(createAPIKeyResult).toEqual({ apiKeysEnabled: false });
});
test('createAPIKey() returns an API key when security is enabled', async () => {
@ -113,7 +120,10 @@ test('createAPIKey() returns an API key when security is enabled', async () => {
securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce({ api_key: '123', id: 'abc' });
const createAPIKeyResult = await constructorCall.createAPIKey();
expect(createAPIKeyResult).toEqual({ created: true, result: { api_key: '123', id: 'abc' } });
expect(createAPIKeyResult).toEqual({
apiKeysEnabled: true,
result: { api_key: '123', id: 'abc' },
});
});
test('createAPIKey() throws when security plugin createAPIKey throws an error', async () => {

View file

@ -6,10 +6,12 @@
import Hapi from 'hapi';
import uuid from 'uuid';
import { AlertTypeRegistry } from '../types';
import { AlertsClient } from '../alerts_client';
import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from '../types';
import { SecurityPluginStartContract, TaskManagerStartContract } from '../shim';
import { KibanaRequest, Logger } from '../../../../../../src/core/server';
import { InvalidateAPIKeyParams } from '../../../../../plugins/security/server';
import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server';
export interface ConstructorOpts {
logger: Logger;
@ -17,6 +19,8 @@ export interface ConstructorOpts {
alertTypeRegistry: AlertTypeRegistry;
securityPluginSetup?: SecurityPluginStartContract;
getSpaceId: (request: Hapi.Request) => string | undefined;
spaceIdToNamespace: SpaceIdToNamespaceFunction;
encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract;
}
export class AlertsClientFactory {
@ -25,6 +29,8 @@ export class AlertsClientFactory {
private readonly alertTypeRegistry: AlertTypeRegistry;
private readonly securityPluginSetup?: SecurityPluginStartContract;
private readonly getSpaceId: (request: Hapi.Request) => string | undefined;
private readonly spaceIdToNamespace: SpaceIdToNamespaceFunction;
private readonly encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract;
constructor(options: ConstructorOpts) {
this.logger = options.logger;
@ -32,16 +38,21 @@ export class AlertsClientFactory {
this.taskManager = options.taskManager;
this.alertTypeRegistry = options.alertTypeRegistry;
this.securityPluginSetup = options.securityPluginSetup;
this.spaceIdToNamespace = options.spaceIdToNamespace;
this.encryptedSavedObjectsPlugin = options.encryptedSavedObjectsPlugin;
}
public create(request: KibanaRequest, legacyRequest: Hapi.Request): AlertsClient {
const { securityPluginSetup } = this;
const spaceId = this.getSpaceId(legacyRequest);
return new AlertsClient({
spaceId,
logger: this.logger,
taskManager: this.taskManager,
alertTypeRegistry: this.alertTypeRegistry,
savedObjectsClient: legacyRequest.getSavedObjectsClient(),
spaceId: this.getSpaceId(legacyRequest),
namespace: this.spaceIdToNamespace(spaceId),
encryptedSavedObjectsPlugin: this.encryptedSavedObjectsPlugin,
async getUserName() {
if (!securityPluginSetup) {
return null;
@ -51,20 +62,37 @@ export class AlertsClientFactory {
},
async createAPIKey() {
if (!securityPluginSetup) {
return { created: false };
return { apiKeysEnabled: false };
}
const createAPIKeyResult = await securityPluginSetup.authc.createAPIKey(request, {
name: `source: alerting, generated uuid: "${uuid.v4()}"`,
role_descriptors: {},
});
if (!createAPIKeyResult) {
return { created: false };
return { apiKeysEnabled: false };
}
return {
created: true,
apiKeysEnabled: true,
result: createAPIKeyResult,
};
},
async invalidateAPIKey(params: InvalidateAPIKeyParams) {
if (!securityPluginSetup) {
return { apiKeysEnabled: false };
}
const invalidateAPIKeyResult = await securityPluginSetup.authc.invalidateAPIKey(
request,
params
);
// Null when Elasticsearch security is disabled
if (!invalidateAPIKeyResult) {
return { apiKeysEnabled: false };
}
return {
apiKeysEnabled: true,
result: invalidateAPIKeyResult,
};
},
});
}
}

View file

@ -107,11 +107,18 @@ export class Plugin {
public start(core: AlertingCoreStart, plugins: AlertingPluginsStart): PluginStartContract {
const { adminClient, serverBasePath } = this;
function spaceIdToNamespace(spaceId?: string): string | undefined {
const spacesPlugin = plugins.spaces();
return spacesPlugin && spaceId ? spacesPlugin.spaceIdToNamespace(spaceId) : undefined;
}
const alertsClientFactory = new AlertsClientFactory({
alertTypeRegistry: this.alertTypeRegistry!,
logger: this.logger,
taskManager: plugins.task_manager,
securityPluginSetup: plugins.security,
encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects,
spaceIdToNamespace,
getSpaceId(request: Hapi.Request) {
const spacesPlugin = plugins.spaces();
return spacesPlugin ? spacesPlugin.getSpaceId(request) : undefined;
@ -127,12 +134,9 @@ export class Plugin {
savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(request),
};
},
spaceIdToNamespace,
executeAction: plugins.actions.execute,
encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects,
spaceIdToNamespace(spaceId?: string): string | undefined {
const spacesPlugin = plugins.spaces();
return spacesPlugin && spaceId ? spacesPlugin.spaceIdToNamespace(spaceId) : undefined;
},
getBasePath(spaceId?: string): string {
const spacesPlugin = plugins.spaces();
return spacesPlugin && spaceId ? spacesPlugin.getBasePath(spaceId) : serverBasePath!;

View file

@ -10,7 +10,13 @@ import { Plugin } from './plugin';
// These exports are part of public Security plugin contract, any change in signature of exported
// functions or removal of exports should be considered as a breaking change.
export { AuthenticationResult, DeauthenticationResult, CreateAPIKeyResult } from './authentication';
export {
AuthenticationResult,
DeauthenticationResult,
CreateAPIKeyResult,
InvalidateAPIKeyParams,
InvalidateAPIKeyResult,
} from './authentication';
export { PluginSetupContract } from './plugin';
export const config = { schema: ConfigSchema };