mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[ResponseOps][Connectors] SSL for Cases Webhook (#185925)
Fixes #180255
## Summary
Adds API support and UI for CA and client-side SSL certificate
authentication to the Cases webhook connector.
<img width="977" alt="aux"
src="03495377
-edfb-4f02-9fd1-3e0ca1d2b0fb">
This PR is to merge a feature branch into `main`.
This feature branch is composed of the following PRs:
- https://github.com/elastic/kibana/pull/183711
- https://github.com/elastic/kibana/pull/183919
- https://github.com/elastic/kibana/pull/184313
### How to test
@cnasikas kindly provided a node server that can be setup locally to
test the certificates against.
Ping me offline and i will send you the rar. You will need to configure
a connector of type `Cases Webhook connector`.
#### Configuring `Authentication`:
The project folder has two sets of keys, one for Alice and one for Bob.
The Alice keys should work and the Bob keys are expected to be
unauthorized.
- For `CRT and KEY File` use:
- `alice_cert.pem` and `alice_key.pem` respectively (or `bob_*`)
- For `PFX File` use:
- `alice.p12` or `bob.p12`.
- Toggle `Add certificate authority`.
- Select `Verification mode` to be `none`.
#### Configuring `Create case`:
Only the URL is relevant. It should be
`https://localhost:9999/authenticate`.
Everything else can have whatever values you want.
### Release Notes
The Cases webhook connector now supports SSL certificate authentication.
This commit is contained in:
parent
cf67fede6e
commit
3a2e1621f4
40 changed files with 3333 additions and 1473 deletions
|
@ -1139,6 +1139,117 @@ Object {
|
|||
"presence": "optional",
|
||||
},
|
||||
"keys": Object {
|
||||
"authType": Object {
|
||||
"flags": Object {
|
||||
"default": [Function],
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
"webhook-authentication-basic",
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
"webhook-authentication-ssl",
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
null,
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"metas": Array [
|
||||
Object {
|
||||
"x-oas-optional": true,
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"ca": Object {
|
||||
"flags": Object {
|
||||
"default": [Function],
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"metas": Array [
|
||||
Object {
|
||||
"x-oas-optional": true,
|
||||
},
|
||||
],
|
||||
"rules": Array [
|
||||
Object {
|
||||
"args": Object {
|
||||
"method": [Function],
|
||||
},
|
||||
"name": "custom",
|
||||
},
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
"certType": Object {
|
||||
"flags": Object {
|
||||
"default": [Function],
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
"ssl-crt-key",
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
"ssl-pfx",
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"metas": Array [
|
||||
Object {
|
||||
"x-oas-optional": true,
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"createCommentJson": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
|
@ -1399,7 +1510,7 @@ Object {
|
|||
},
|
||||
"headers": Object {
|
||||
"flags": Object {
|
||||
"default": [Function],
|
||||
"default": null,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
|
@ -1541,6 +1652,57 @@ Object {
|
|||
],
|
||||
"type": "string",
|
||||
},
|
||||
"verificationMode": Object {
|
||||
"flags": Object {
|
||||
"default": [Function],
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
"none",
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
"certificate",
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
"full",
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"metas": Array [
|
||||
Object {
|
||||
"x-oas-optional": true,
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"viewIncidentUrl": Object {
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
|
@ -1575,6 +1737,82 @@ Object {
|
|||
"presence": "optional",
|
||||
},
|
||||
"keys": Object {
|
||||
"crt": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
},
|
||||
"rules": Array [
|
||||
Object {
|
||||
"args": Object {
|
||||
"method": [Function],
|
||||
},
|
||||
"name": "custom",
|
||||
},
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
null,
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"key": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
},
|
||||
"rules": Array [
|
||||
Object {
|
||||
"args": Object {
|
||||
"method": [Function],
|
||||
},
|
||||
"name": "custom",
|
||||
},
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
null,
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"password": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
|
@ -1613,6 +1851,44 @@ Object {
|
|||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"pfx": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
"matches": Array [
|
||||
Object {
|
||||
"schema": Object {
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
},
|
||||
"rules": Array [
|
||||
Object {
|
||||
"args": Object {
|
||||
"method": [Function],
|
||||
},
|
||||
"name": "custom",
|
||||
},
|
||||
],
|
||||
"type": "string",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"schema": Object {
|
||||
"allow": Array [
|
||||
null,
|
||||
],
|
||||
"flags": Object {
|
||||
"error": [Function],
|
||||
"only": true,
|
||||
},
|
||||
"type": "any",
|
||||
},
|
||||
},
|
||||
],
|
||||
"type": "alternatives",
|
||||
},
|
||||
"user": Object {
|
||||
"flags": Object {
|
||||
"default": null,
|
||||
|
@ -1657,6 +1933,14 @@ Object {
|
|||
"objects": false,
|
||||
},
|
||||
},
|
||||
"rules": Array [
|
||||
Object {
|
||||
"args": Object {
|
||||
"method": [Function],
|
||||
},
|
||||
"name": "custom",
|
||||
},
|
||||
],
|
||||
"type": "object",
|
||||
}
|
||||
`;
|
||||
|
@ -32515,7 +32799,7 @@ Object {
|
|||
},
|
||||
"headers": Object {
|
||||
"flags": Object {
|
||||
"default": [Function],
|
||||
"default": null,
|
||||
"error": [Function],
|
||||
"presence": "optional",
|
||||
},
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
export enum WebhookAuthType {
|
||||
export enum AuthType {
|
||||
Basic = 'webhook-authentication-basic',
|
||||
SSL = 'webhook-authentication-ssl',
|
||||
}
|
||||
|
@ -14,3 +14,9 @@ export enum SSLCertType {
|
|||
CRT = 'ssl-crt-key',
|
||||
PFX = 'ssl-pfx',
|
||||
}
|
||||
|
||||
export enum WebhookMethods {
|
||||
PATCH = 'patch',
|
||||
POST = 'post',
|
||||
PUT = 'put',
|
||||
}
|
61
x-pack/plugins/stack_connectors/common/auth/schema.ts
Normal file
61
x-pack/plugins/stack_connectors/common/auth/schema.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { AuthType, SSLCertType } from './constants';
|
||||
|
||||
export const authTypeSchema = schema.maybe(
|
||||
schema.oneOf(
|
||||
[schema.literal(AuthType.Basic), schema.literal(AuthType.SSL), schema.literal(null)],
|
||||
{
|
||||
defaultValue: AuthType.Basic,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const hasAuthSchema = schema.boolean({ defaultValue: true });
|
||||
|
||||
export const AuthConfiguration = {
|
||||
hasAuth: hasAuthSchema,
|
||||
authType: authTypeSchema,
|
||||
certType: schema.maybe(
|
||||
schema.oneOf([schema.literal(SSLCertType.CRT), schema.literal(SSLCertType.PFX)])
|
||||
),
|
||||
ca: schema.maybe(schema.string()),
|
||||
verificationMode: schema.maybe(
|
||||
schema.oneOf([schema.literal('none'), schema.literal('certificate'), schema.literal('full')])
|
||||
),
|
||||
};
|
||||
|
||||
export const SecretConfiguration = {
|
||||
user: schema.nullable(schema.string()),
|
||||
password: schema.nullable(schema.string()),
|
||||
crt: schema.nullable(schema.string()),
|
||||
key: schema.nullable(schema.string()),
|
||||
pfx: schema.nullable(schema.string()),
|
||||
};
|
||||
|
||||
export const SecretConfigurationSchemaValidation = {
|
||||
validate: (secrets: any) => {
|
||||
// user and password must be set together (or not at all)
|
||||
if (!secrets.password && !secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return;
|
||||
if (secrets.password && secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return;
|
||||
if (secrets.crt && secrets.key && !secrets.user && !secrets.pfx) return;
|
||||
if (!secrets.crt && !secrets.key && !secrets.user && secrets.pfx) return;
|
||||
return i18n.translate('xpack.stackConnectors.webhook.invalidSecrets', {
|
||||
defaultMessage:
|
||||
'must specify one of the following schemas: user and password; crt and key (with optional password); or pfx (with optional password)',
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export const SecretConfigurationSchema = schema.object(
|
||||
SecretConfiguration,
|
||||
SecretConfigurationSchemaValidation
|
||||
);
|
20
x-pack/plugins/stack_connectors/common/auth/types.ts
Normal file
20
x-pack/plugins/stack_connectors/common/auth/types.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import {
|
||||
AuthConfiguration,
|
||||
authTypeSchema,
|
||||
hasAuthSchema,
|
||||
SecretConfigurationSchema,
|
||||
} from './schema';
|
||||
|
||||
export type HasAuth = TypeOf<typeof hasAuthSchema>;
|
||||
export type AuthTypeName = TypeOf<typeof authTypeSchema>;
|
||||
export type SecretsConfigurationType = TypeOf<typeof SecretConfigurationSchema>;
|
||||
export type CAType = TypeOf<typeof AuthConfiguration.ca>;
|
||||
export type VerificationModeType = TypeOf<typeof AuthConfiguration.verificationMode>;
|
209
x-pack/plugins/stack_connectors/common/auth/utils.test.ts
Normal file
209
x-pack/plugins/stack_connectors/common/auth/utils.test.ts
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { AuthType } from './constants';
|
||||
import { buildConnectorAuth, isBasicAuth, validateConnectorAuthConfiguration } from './utils';
|
||||
|
||||
describe('utils', () => {
|
||||
describe('isBasicAuth', () => {
|
||||
it('returns false when hasAuth is false and authType is undefined', () => {
|
||||
expect(
|
||||
isBasicAuth({
|
||||
hasAuth: false,
|
||||
authType: undefined,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when hasAuth is false and authType is basic', () => {
|
||||
expect(
|
||||
isBasicAuth({
|
||||
hasAuth: false,
|
||||
authType: AuthType.Basic,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns false when hasAuth is true and authType is ssl', () => {
|
||||
expect(
|
||||
isBasicAuth({
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
})
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('returns true when hasAuth is true and authType is undefined', () => {
|
||||
expect(
|
||||
isBasicAuth({
|
||||
hasAuth: true,
|
||||
authType: undefined,
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it('returns true when hasAuth is true and authType is basic', () => {
|
||||
expect(
|
||||
isBasicAuth({
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
})
|
||||
).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('validateConnectorAuthConfiguration', () => {
|
||||
it('does not throw with correct authType=basic params', () => {
|
||||
expect(() =>
|
||||
validateConnectorAuthConfiguration({
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
basicAuth: { auth: { username: 'foo', password: 'bar' } },
|
||||
sslOverrides: {},
|
||||
connectorName: 'foobar',
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('does not throw with correct authType=undefined params', () => {
|
||||
expect(() =>
|
||||
validateConnectorAuthConfiguration({
|
||||
hasAuth: true,
|
||||
authType: undefined,
|
||||
basicAuth: { auth: { username: 'foo', password: 'bar' } },
|
||||
sslOverrides: {},
|
||||
connectorName: 'foobar',
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws when type is basic and the username is missing', () => {
|
||||
expect(() =>
|
||||
validateConnectorAuthConfiguration({
|
||||
hasAuth: true,
|
||||
authType: undefined,
|
||||
// @ts-ignore: that's what we are testing
|
||||
basicAuth: { auth: { password: 'bar' } },
|
||||
sslOverrides: {},
|
||||
connectorName: 'Foobar',
|
||||
})
|
||||
).toThrow('[Action]Foobar: Wrong configuration.');
|
||||
});
|
||||
|
||||
it('throws when type is basic and the password is missing', () => {
|
||||
expect(() =>
|
||||
validateConnectorAuthConfiguration({
|
||||
hasAuth: true,
|
||||
authType: undefined,
|
||||
// @ts-ignore: that's what we are testing
|
||||
basicAuth: { auth: { username: 'foo' } },
|
||||
sslOverrides: {},
|
||||
connectorName: 'Foobar',
|
||||
})
|
||||
).toThrow('[Action]Foobar: Wrong configuration.');
|
||||
});
|
||||
|
||||
it('does not throw with correct authType=ssl params', () => {
|
||||
expect(() =>
|
||||
validateConnectorAuthConfiguration({
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
basicAuth: {},
|
||||
sslOverrides: { verificationMode: 'none', passphrase: 'passphrase' },
|
||||
connectorName: 'foobar',
|
||||
})
|
||||
).not.toThrow();
|
||||
});
|
||||
|
||||
it('throws when type is SSL and the sslOverrides are missing', () => {
|
||||
expect(() =>
|
||||
validateConnectorAuthConfiguration({
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
basicAuth: {},
|
||||
sslOverrides: {},
|
||||
connectorName: 'Foobar',
|
||||
})
|
||||
).toThrow('[Action]Foobar: Wrong configuration.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildConnectorAuth', () => {
|
||||
it('returns empty objects when hasAuth=false', () => {
|
||||
expect(
|
||||
buildConnectorAuth({
|
||||
hasAuth: false,
|
||||
authType: AuthType.SSL,
|
||||
secrets: { user: 'foo', password: 'bar', crt: null, key: null, pfx: null },
|
||||
verificationMode: undefined,
|
||||
ca: undefined,
|
||||
})
|
||||
).toEqual({ basicAuth: {}, sslOverrides: {} });
|
||||
});
|
||||
|
||||
it('builds basicAuth correctly with authType=basic', () => {
|
||||
expect(
|
||||
buildConnectorAuth({
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
secrets: { user: 'foo', password: 'bar', crt: null, key: null, pfx: null },
|
||||
verificationMode: undefined,
|
||||
ca: undefined,
|
||||
})
|
||||
).toEqual({ basicAuth: { auth: { username: 'foo', password: 'bar' } }, sslOverrides: {} });
|
||||
});
|
||||
|
||||
it('builds basicAuth correctly with hasAuth=true and authType=undefined', () => {
|
||||
expect(
|
||||
buildConnectorAuth({
|
||||
hasAuth: true,
|
||||
authType: undefined,
|
||||
secrets: { user: 'foo', password: 'bar', crt: null, key: null, pfx: null },
|
||||
verificationMode: undefined,
|
||||
ca: undefined,
|
||||
})
|
||||
).toEqual({ basicAuth: { auth: { username: 'foo', password: 'bar' } }, sslOverrides: {} });
|
||||
});
|
||||
|
||||
it('builds sslOverrides correctly with authType=ssl', () => {
|
||||
expect(
|
||||
buildConnectorAuth({
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
secrets: { user: 'foo', password: 'bar', crt: 'null', key: 'null', pfx: 'null' },
|
||||
verificationMode: 'certificate',
|
||||
ca: 'foobar?',
|
||||
})
|
||||
).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"basicAuth": Object {},
|
||||
"sslOverrides": Object {
|
||||
"ca": Object {
|
||||
"data": Array [
|
||||
126,
|
||||
138,
|
||||
27,
|
||||
106,
|
||||
],
|
||||
"type": "Buffer",
|
||||
},
|
||||
"passphrase": "bar",
|
||||
"pfx": Object {
|
||||
"data": Array [
|
||||
158,
|
||||
233,
|
||||
101,
|
||||
],
|
||||
"type": "Buffer",
|
||||
},
|
||||
"verificationMode": "certificate",
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
});
|
101
x-pack/plugins/stack_connectors/common/auth/utils.ts
Normal file
101
x-pack/plugins/stack_connectors/common/auth/utils.ts
Normal file
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import { isString, isEmpty } from 'lodash';
|
||||
|
||||
import type { SSLSettings } from '@kbn/actions-plugin/server/types';
|
||||
import type {
|
||||
AuthTypeName,
|
||||
CAType,
|
||||
HasAuth,
|
||||
SecretsConfigurationType,
|
||||
VerificationModeType,
|
||||
} from './types';
|
||||
|
||||
import { AuthType } from './constants';
|
||||
|
||||
// For backwards compatibility with connectors created before authType was added, interpret a
|
||||
// hasAuth: true and undefined authType as basic auth
|
||||
export const isBasicAuth = ({
|
||||
hasAuth,
|
||||
authType,
|
||||
}: {
|
||||
hasAuth: HasAuth;
|
||||
authType: AuthTypeName;
|
||||
}): boolean => hasAuth && (authType === AuthType.Basic || !authType);
|
||||
|
||||
interface BasicAuthResponse {
|
||||
auth?: { username: string; password: string };
|
||||
}
|
||||
|
||||
export const buildConnectorAuth = ({
|
||||
hasAuth,
|
||||
authType,
|
||||
secrets,
|
||||
verificationMode,
|
||||
ca,
|
||||
}: {
|
||||
hasAuth: HasAuth;
|
||||
authType: AuthTypeName;
|
||||
secrets: SecretsConfigurationType;
|
||||
verificationMode: VerificationModeType;
|
||||
ca: CAType;
|
||||
}): { basicAuth: BasicAuthResponse; sslOverrides: SSLSettings } => {
|
||||
let basicAuth: BasicAuthResponse = {};
|
||||
let sslOverrides: SSLSettings = {};
|
||||
let sslCertificate = {};
|
||||
|
||||
if (isBasicAuth({ hasAuth, authType })) {
|
||||
basicAuth =
|
||||
isString(secrets.user) && isString(secrets.password)
|
||||
? { auth: { username: secrets.user, password: secrets.password } }
|
||||
: {};
|
||||
} else if (hasAuth && authType === AuthType.SSL) {
|
||||
sslCertificate =
|
||||
(isString(secrets.crt) && isString(secrets.key)) || isString(secrets.pfx)
|
||||
? isString(secrets.pfx)
|
||||
? {
|
||||
pfx: Buffer.from(secrets.pfx, 'base64'),
|
||||
...(isString(secrets.password) ? { passphrase: secrets.password } : {}),
|
||||
}
|
||||
: {
|
||||
cert: Buffer.from(secrets.crt!, 'base64'),
|
||||
key: Buffer.from(secrets.key!, 'base64'),
|
||||
...(isString(secrets.password) ? { passphrase: secrets.password } : {}),
|
||||
}
|
||||
: {};
|
||||
}
|
||||
|
||||
sslOverrides = {
|
||||
...sslCertificate,
|
||||
...(verificationMode ? { verificationMode } : {}),
|
||||
...(ca ? { ca: Buffer.from(ca, 'base64') } : {}),
|
||||
};
|
||||
|
||||
return { basicAuth, sslOverrides };
|
||||
};
|
||||
|
||||
export const validateConnectorAuthConfiguration = ({
|
||||
hasAuth,
|
||||
authType,
|
||||
basicAuth,
|
||||
sslOverrides,
|
||||
connectorName,
|
||||
}: {
|
||||
hasAuth: HasAuth;
|
||||
authType: AuthTypeName;
|
||||
basicAuth: BasicAuthResponse;
|
||||
sslOverrides: SSLSettings;
|
||||
connectorName: string;
|
||||
}) => {
|
||||
if (
|
||||
(isBasicAuth({ hasAuth, authType }) &&
|
||||
(!basicAuth.auth?.password || !basicAuth.auth?.username)) ||
|
||||
(authType === AuthType.SSL && isEmpty(sslOverrides))
|
||||
) {
|
||||
throw Error(`[Action]${connectorName}: Wrong configuration.`);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,483 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { AuthConfig } from './auth_config';
|
||||
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { AuthType, SSLCertType } from '../../../common/auth/constants';
|
||||
import { AuthFormTestProvider } from '../../connector_types/lib/test_utils';
|
||||
|
||||
describe('AuthConfig renders', () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
it('renders all fields for authType=None', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
hasAuth: false,
|
||||
},
|
||||
__internal__: {
|
||||
hasCA: true,
|
||||
hasHeaders: true,
|
||||
},
|
||||
};
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookHeaderText')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookHeadersKeyInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookHeadersValueInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookAddHeaderButton')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCAInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookVerificationModeSelect')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authNone')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authBasic')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('basicAuthFields')).not.toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authSSL')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('sslCertFields')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('toggles headers as expected', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
hasAuth: false,
|
||||
},
|
||||
__internal__: {
|
||||
hasCA: false,
|
||||
hasHeaders: false,
|
||||
},
|
||||
};
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
const headersToggle = await screen.findByTestId('webhookViewHeadersSwitch');
|
||||
|
||||
expect(headersToggle).toBeInTheDocument();
|
||||
|
||||
userEvent.click(headersToggle);
|
||||
|
||||
expect(await screen.findByTestId('webhookHeaderText')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookHeadersKeyInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookHeadersValueInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookAddHeaderButton')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('toggles CA as expected', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
hasAuth: false,
|
||||
},
|
||||
__internal__: {
|
||||
hasCA: false,
|
||||
hasHeaders: false,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
const caToggle = await screen.findByTestId('webhookViewCASwitch');
|
||||
|
||||
expect(caToggle).toBeInTheDocument();
|
||||
|
||||
userEvent.click(caToggle);
|
||||
|
||||
expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCAInput')).toBeInTheDocument();
|
||||
|
||||
const verificationModeSelect = await screen.findByTestId('webhookVerificationModeSelect');
|
||||
|
||||
expect(verificationModeSelect).toBeInTheDocument();
|
||||
|
||||
['None', 'Certificate', 'Full'].forEach((optionName) => {
|
||||
const select = within(verificationModeSelect);
|
||||
|
||||
expect(select.getByRole('option', { name: optionName }));
|
||||
});
|
||||
});
|
||||
|
||||
it('renders all fields for authType=Basic', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authNone')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authBasic')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('basicAuthFields')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authSSL')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('sslCertFields')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders all fields for authType=SSL', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
crt: Buffer.from('some binary string').toString('base64'),
|
||||
key: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authNone')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authBasic')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('basicAuthFields')).not.toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authSSL')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders all fields for authType=SSL and certType=PFX', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.PFX,
|
||||
},
|
||||
secrets: {
|
||||
crt: Buffer.from('some binary string').toString('base64'),
|
||||
key: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('webhookViewHeadersSwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookViewCASwitch')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authNone')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authBasic')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('basicAuthFields')).not.toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authSSL')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Validation', () => {
|
||||
const defaultTestFormData = {
|
||||
config: {
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
hasAuth: true,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('succeeds with hasAuth=True', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
hasAuth: true,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
};
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
hasCA: false,
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with hasAuth=false', async () => {
|
||||
const testFormData = {
|
||||
config: {
|
||||
...defaultTestFormData.config,
|
||||
hasAuth: false,
|
||||
},
|
||||
};
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
hasAuth: false,
|
||||
authType: null,
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
hasCA: false,
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds without headers', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: false,
|
||||
hasCA: false,
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with CA and verificationMode', async () => {
|
||||
const testConfig = {
|
||||
...defaultTestFormData,
|
||||
config: {
|
||||
...defaultTestFormData.config,
|
||||
ca: Buffer.from('some binary string').toString('base64'),
|
||||
verificationMode: 'full',
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.Basic,
|
||||
ca: Buffer.from('some binary string').toString('base64'),
|
||||
verificationMode: 'full',
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
hasCA: true,
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('fails with hasCa=true and a missing CA', async () => {
|
||||
const testConfig = {
|
||||
...defaultTestFormData,
|
||||
config: {
|
||||
...defaultTestFormData.config,
|
||||
verificationMode: 'full',
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
hasCA: true,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {},
|
||||
isValid: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with authType=SSL and a CRT and KEY', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
...defaultTestFormData.config,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
crt: Buffer.from('some binary string').toString('base64'),
|
||||
key: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.CRT,
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
},
|
||||
secrets: {
|
||||
crt: Buffer.from('some binary string').toString('base64'),
|
||||
key: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
hasCA: false,
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('succeeds with authType=SSL and a PFX', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
...defaultTestFormData.config,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.PFX,
|
||||
},
|
||||
secrets: {
|
||||
pfx: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<AuthConfig readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
hasAuth: true,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.PFX,
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
},
|
||||
secrets: {
|
||||
pfx: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
hasCA: false,
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,292 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { FunctionComponent, useEffect } from 'react';
|
||||
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiCallOut,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
UseArray,
|
||||
UseField,
|
||||
useFormContext,
|
||||
useFormData,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import {
|
||||
ToggleField,
|
||||
TextField,
|
||||
CardRadioGroupField,
|
||||
HiddenField,
|
||||
FilePickerField,
|
||||
SelectField,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
|
||||
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
|
||||
import { AuthType, SSLCertType } from '../../../common/auth/constants';
|
||||
import { SSLCertFields } from './ssl_cert_fields';
|
||||
import { BasicAuthFields } from './basic_auth_fields';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props {
|
||||
readOnly: boolean;
|
||||
hideSSL?: boolean;
|
||||
}
|
||||
|
||||
const { emptyField } = fieldValidators;
|
||||
|
||||
const VERIFICATION_MODE_DEFAULT = 'full';
|
||||
|
||||
export const AuthConfig: FunctionComponent<Props> = ({ readOnly, hideSSL }) => {
|
||||
const { setFieldValue, getFieldDefaultValue } = useFormContext();
|
||||
const [{ config, __internal__ }] = useFormData({
|
||||
watch: [
|
||||
'config.hasAuth',
|
||||
'config.authType',
|
||||
'config.certType',
|
||||
'config.verificationMode',
|
||||
'__internal__.hasHeaders',
|
||||
'__internal__.hasCA',
|
||||
],
|
||||
});
|
||||
|
||||
const authType = config == null ? AuthType.Basic : config.authType;
|
||||
const certType = config == null ? SSLCertType.CRT : config.certType;
|
||||
const hasHeaders = __internal__ != null ? __internal__.hasHeaders : false;
|
||||
const hasCA = __internal__ != null ? __internal__.hasCA : false;
|
||||
const hasInitialCA = !!getFieldDefaultValue<boolean | undefined>('config.ca');
|
||||
const hasHeadersDefaultValue = !!getFieldDefaultValue<boolean | undefined>('config.headers');
|
||||
const authTypeDefaultValue =
|
||||
getFieldDefaultValue('config.hasAuth') === false
|
||||
? null
|
||||
: getFieldDefaultValue('config.authType') ?? AuthType.Basic;
|
||||
const certTypeDefaultValue: SSLCertType =
|
||||
getFieldDefaultValue('config.certType') ?? SSLCertType.CRT;
|
||||
const hasCADefaultValue =
|
||||
!!getFieldDefaultValue<boolean | undefined>('config.ca') ||
|
||||
getFieldDefaultValue('config.verificationMode') === 'none';
|
||||
|
||||
useEffect(() => setFieldValue('config.hasAuth', Boolean(authType)), [authType, setFieldValue]);
|
||||
|
||||
const hideSSLFields = hideSSL && authType !== AuthType.SSL;
|
||||
|
||||
const authOptions = [
|
||||
{
|
||||
value: null,
|
||||
label: i18n.AUTHENTICATION_NONE,
|
||||
'data-test-subj': 'authNone',
|
||||
},
|
||||
{
|
||||
value: AuthType.Basic,
|
||||
label: i18n.AUTHENTICATION_BASIC,
|
||||
children: authType === AuthType.Basic && <BasicAuthFields readOnly={readOnly} />,
|
||||
'data-test-subj': 'authBasic',
|
||||
},
|
||||
];
|
||||
|
||||
if (!hideSSLFields) {
|
||||
authOptions.push({
|
||||
value: AuthType.SSL,
|
||||
label: i18n.AUTHENTICATION_SSL,
|
||||
children: authType === AuthType.SSL && (
|
||||
<SSLCertFields
|
||||
readOnly={readOnly}
|
||||
certTypeDefaultValue={certTypeDefaultValue}
|
||||
certType={certType}
|
||||
/>
|
||||
),
|
||||
'data-test-subj': 'authSSL',
|
||||
});
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<h4>{i18n.AUTHENTICATION_TITLE}</h4>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer size="m" />
|
||||
<UseField path="config.hasAuth" component={HiddenField} />
|
||||
<UseField
|
||||
path="config.authType"
|
||||
defaultValue={authTypeDefaultValue}
|
||||
component={CardRadioGroupField}
|
||||
componentProps={{
|
||||
options: authOptions,
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
<UseField
|
||||
path="__internal__.hasHeaders"
|
||||
component={ToggleField}
|
||||
config={{
|
||||
defaultValue: hasHeadersDefaultValue,
|
||||
label: i18n.HEADERS_SWITCH,
|
||||
}}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
disabled: readOnly,
|
||||
'data-test-subj': 'webhookViewHeadersSwitch',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{hasHeaders && (
|
||||
<UseArray path="config.headers" initialNumberOfItems={1}>
|
||||
{({ items, addItem, removeItem }) => {
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="xxs" data-test-subj="webhookHeaderText">
|
||||
<h5>{i18n.HEADERS_TITLE}</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{items.map((item) => (
|
||||
<EuiFlexGroup key={item.id}>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path={`${item.path}.key`}
|
||||
config={{
|
||||
label: i18n.KEY_LABEL,
|
||||
}}
|
||||
component={TextField}
|
||||
// This is needed because when you delete
|
||||
// a row and add a new one, the stale values will appear
|
||||
readDefaultValueOnForm={!item.isNew}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly, ['data-test-subj']: 'webhookHeadersKeyInput' },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path={`${item.path}.value`}
|
||||
config={{ label: i18n.VALUE_LABEL }}
|
||||
component={TextField}
|
||||
readDefaultValueOnForm={!item.isNew}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
readOnly,
|
||||
['data-test-subj']: 'webhookHeadersValueInput',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
color="danger"
|
||||
onClick={() => removeItem(item.id)}
|
||||
iconType="minusInCircle"
|
||||
aria-label={i18n.DELETE_BUTTON}
|
||||
style={{ marginTop: '28px' }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButtonEmpty
|
||||
iconType="plusInCircle"
|
||||
onClick={addItem}
|
||||
data-test-subj="webhookAddHeaderButton"
|
||||
>
|
||||
{i18n.ADD_BUTTON}
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</UseArray>
|
||||
)}
|
||||
<EuiSpacer size="m" />
|
||||
{!hideSSLFields && (
|
||||
<>
|
||||
<UseField
|
||||
path="__internal__.hasCA"
|
||||
component={ToggleField}
|
||||
config={{ defaultValue: hasCADefaultValue, label: i18n.ADD_CA_LABEL }}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
disabled: readOnly,
|
||||
'data-test-subj': 'webhookViewCASwitch',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{hasCA && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="config.ca"
|
||||
config={{
|
||||
label: 'CA file',
|
||||
validations: [
|
||||
{
|
||||
validator:
|
||||
config?.verificationMode !== 'none'
|
||||
? emptyField(i18n.CA_REQUIRED)
|
||||
: () => {},
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
display: 'default',
|
||||
'data-test-subj': 'webhookCAInput',
|
||||
accept: '.ca,.pem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="config.verificationMode"
|
||||
component={SelectField}
|
||||
config={{
|
||||
label: i18n.VERIFICATION_MODE_LABEL,
|
||||
defaultValue: VERIFICATION_MODE_DEFAULT,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.VERIFICATION_MODE_LABEL),
|
||||
},
|
||||
],
|
||||
}}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookVerificationModeSelect',
|
||||
options: [
|
||||
{ text: 'None', value: 'none' },
|
||||
{ text: 'Certificate', value: 'certificate' },
|
||||
{ text: 'Full', value: 'full' },
|
||||
],
|
||||
fullWidth: true,
|
||||
readOnly,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{hasInitialCA && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut size="s" iconType="document" title={i18n.EDIT_CA_CALLOUT} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { BasicAuthFields } from './basic_auth_fields';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { AuthFormTestProvider } from '../../connector_types/lib/test_utils';
|
||||
|
||||
describe('BasicAuthFields', () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
it('renders all fields', async () => {
|
||||
const testFormData = {
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testFormData} onSubmit={onSubmit}>
|
||||
<BasicAuthFields readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('basicAuthFields')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookUserInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookPasswordInput')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Validation', () => {
|
||||
const defaultTestFormData = {
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('validation succeeds with correct fields', async () => {
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={defaultTestFormData} onSubmit={onSubmit}>
|
||||
<BasicAuthFields readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'pass',
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validates correctly missing user', async () => {
|
||||
const testConfig = {
|
||||
secrets: {
|
||||
user: '',
|
||||
password: 'password',
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<BasicAuthFields readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false });
|
||||
});
|
||||
});
|
||||
|
||||
it('validates correctly missing password', async () => {
|
||||
const testConfig = {
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: '',
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<BasicAuthFields readOnly={false} />
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false });
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { Field, PasswordField } from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
const { emptyField } = fieldValidators;
|
||||
|
||||
interface BasicAuthProps {
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export const BasicAuthFields: React.FC<BasicAuthProps> = ({ readOnly }) => (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" data-test-subj="basicAuthFields">
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.user"
|
||||
config={{
|
||||
label: i18n.USERNAME,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.USERNAME_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={Field}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly, 'data-test-subj': 'webhookUserInput', fullWidth: true },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.password"
|
||||
config={{
|
||||
label: i18n.PASSWORD,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.PASSWORD_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={PasswordField}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly, 'data-test-subj': 'webhookPasswordInput' },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { SSLCertFields } from './ssl_cert_fields';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { SSLCertType } from '../../../common/auth/constants';
|
||||
import { AuthFormTestProvider } from '../../connector_types/lib/test_utils';
|
||||
|
||||
const certTypeDefaultValue: SSLCertType = SSLCertType.CRT;
|
||||
|
||||
describe('SSLCertFields', () => {
|
||||
const onSubmit = jest.fn();
|
||||
|
||||
it('renders all fields for certType=CRT', async () => {
|
||||
render(
|
||||
<AuthFormTestProvider onSubmit={onSubmit}>
|
||||
<SSLCertFields
|
||||
readOnly={false}
|
||||
certTypeDefaultValue={certTypeDefaultValue}
|
||||
certType={SSLCertType.CRT}
|
||||
/>
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookSSLPassphraseInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCertTypeTabs')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookSSLCRTInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookSSLKEYInput')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders all fields for certType=PFX', async () => {
|
||||
render(
|
||||
<AuthFormTestProvider onSubmit={onSubmit}>
|
||||
<SSLCertFields
|
||||
readOnly={false}
|
||||
certTypeDefaultValue={certTypeDefaultValue}
|
||||
certType={SSLCertType.PFX}
|
||||
/>
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
expect(await screen.findByTestId('sslCertFields')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookSSLPassphraseInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCertTypeTabs')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookSSLPFXInput')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Validation', () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('validates correctly with a PFX', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
certType: SSLCertType.PFX,
|
||||
},
|
||||
secrets: {
|
||||
pfx: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<SSLCertFields
|
||||
readOnly={false}
|
||||
certTypeDefaultValue={SSLCertType.PFX}
|
||||
certType={SSLCertType.PFX}
|
||||
/>
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
certType: SSLCertType.PFX,
|
||||
},
|
||||
secrets: {
|
||||
pfx: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validates correctly a missing PFX', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
certType: SSLCertType.PFX,
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<SSLCertFields
|
||||
readOnly={false}
|
||||
certTypeDefaultValue={SSLCertType.PFX}
|
||||
certType={SSLCertType.PFX}
|
||||
/>
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {},
|
||||
isValid: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validates correctly with a CRT and KEY', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
crt: Buffer.from('some binary string').toString('base64'),
|
||||
key: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<SSLCertFields
|
||||
readOnly={false}
|
||||
certTypeDefaultValue={SSLCertType.CRT}
|
||||
certType={SSLCertType.CRT}
|
||||
/>
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {
|
||||
config: {
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
crt: Buffer.from('some binary string').toString('base64'),
|
||||
key: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validates correctly with a CRT but a missing KEY', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
crt: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<SSLCertFields
|
||||
readOnly={false}
|
||||
certTypeDefaultValue={SSLCertType.CRT}
|
||||
certType={SSLCertType.CRT}
|
||||
/>
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {},
|
||||
isValid: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('validates correctly with a KEY but a missing CRT', async () => {
|
||||
const testConfig = {
|
||||
config: {
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
key: Buffer.from('some binary string').toString('base64'),
|
||||
},
|
||||
};
|
||||
|
||||
render(
|
||||
<AuthFormTestProvider defaultValue={testConfig} onSubmit={onSubmit}>
|
||||
<SSLCertFields
|
||||
readOnly={false}
|
||||
certTypeDefaultValue={SSLCertType.CRT}
|
||||
certType={SSLCertType.CRT}
|
||||
/>
|
||||
</AuthFormTestProvider>
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(onSubmit).toHaveBeenCalledWith({
|
||||
data: {},
|
||||
isValid: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,138 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTab, EuiTabs } from '@elastic/eui';
|
||||
import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { PasswordField } from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
|
||||
import { FilePickerField } from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
|
||||
import { SSLCertType } from '../../../common/auth/constants';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const { emptyField } = fieldValidators;
|
||||
|
||||
interface BasicAuthProps {
|
||||
readOnly: boolean;
|
||||
certTypeDefaultValue: SSLCertType;
|
||||
certType: SSLCertType;
|
||||
}
|
||||
|
||||
export const SSLCertFields: React.FC<BasicAuthProps> = ({
|
||||
readOnly,
|
||||
certTypeDefaultValue,
|
||||
certType,
|
||||
}) => (
|
||||
<EuiFlexGroup justifyContent="spaceBetween" data-test-subj="sslCertFields">
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.password"
|
||||
config={{
|
||||
label: i18n.PASSWORD,
|
||||
}}
|
||||
component={PasswordField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLPassphraseInput',
|
||||
readOnly,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<UseField
|
||||
path="config.certType"
|
||||
defaultValue={certTypeDefaultValue}
|
||||
component={({ field }) => (
|
||||
<EuiTabs size="s" data-test-subj="webhookCertTypeTabs">
|
||||
<EuiTab
|
||||
onClick={() => field.setValue(SSLCertType.CRT)}
|
||||
isSelected={field.value === SSLCertType.CRT}
|
||||
>
|
||||
{i18n.CERT_TYPE_CRT_KEY}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
onClick={() => field.setValue(SSLCertType.PFX)}
|
||||
isSelected={field.value === SSLCertType.PFX}
|
||||
>
|
||||
{i18n.CERT_TYPE_PFX}
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
)}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
{certType === SSLCertType.CRT && (
|
||||
<EuiFlexGroup wrap>
|
||||
<EuiFlexItem css={{ minWidth: 200 }}>
|
||||
<UseField
|
||||
path="secrets.crt"
|
||||
config={{
|
||||
label: 'CRT file',
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.CRT_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLCRTInput',
|
||||
display: 'default',
|
||||
accept: '.crt,.cert,.cer,.pem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem css={{ minWidth: 200 }}>
|
||||
<UseField
|
||||
path="secrets.key"
|
||||
config={{
|
||||
label: 'KEY file',
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.KEY_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLKEYInput',
|
||||
display: 'default',
|
||||
accept: '.key,.pem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{certType === SSLCertType.PFX && (
|
||||
<UseField
|
||||
path="secrets.pfx"
|
||||
config={{
|
||||
label: 'PFX file',
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.PFX_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLPFXInput',
|
||||
display: 'default',
|
||||
accept: '.pfx,.p12',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
|
@ -0,0 +1,159 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const AUTHENTICATION_TITLE = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.authenticationTitle',
|
||||
{
|
||||
defaultMessage: 'Authentication',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_NONE = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.authenticationMethodNoneLabel',
|
||||
{
|
||||
defaultMessage: 'None',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_BASIC = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.authenticationMethodBasicLabel',
|
||||
{
|
||||
defaultMessage: 'Basic authentication',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_SSL = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.authenticationMethodSSLLabel',
|
||||
{
|
||||
defaultMessage: 'SSL authentication',
|
||||
}
|
||||
);
|
||||
|
||||
export const USERNAME = i18n.translate('xpack.stackConnectors.components.auth.userTextFieldLabel', {
|
||||
defaultMessage: 'Username',
|
||||
});
|
||||
|
||||
export const PASSWORD = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.passwordTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Password',
|
||||
}
|
||||
);
|
||||
|
||||
export const USERNAME_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.error.requiredAuthUserNameText',
|
||||
{
|
||||
defaultMessage: 'Username is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PASSWORD_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.error.requiredAuthPasswordText',
|
||||
{
|
||||
defaultMessage: 'Password is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CERT_TYPE_CRT_KEY = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.certTypeCrtKeyLabel',
|
||||
{
|
||||
defaultMessage: 'CRT and KEY file',
|
||||
}
|
||||
);
|
||||
export const CERT_TYPE_PFX = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.certTypePfxLabel',
|
||||
{
|
||||
defaultMessage: 'PFX file',
|
||||
}
|
||||
);
|
||||
|
||||
export const CRT_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.error.requiredCRTText',
|
||||
{
|
||||
defaultMessage: 'CRT file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const KEY_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.error.requiredKEYText',
|
||||
{
|
||||
defaultMessage: 'KEY file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PFX_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.error.requiredPFXText',
|
||||
{
|
||||
defaultMessage: 'PFX file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const HEADERS_SWITCH = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.viewHeadersSwitch',
|
||||
{
|
||||
defaultMessage: 'Add HTTP header',
|
||||
}
|
||||
);
|
||||
|
||||
export const HEADERS_TITLE = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.httpHeadersTitle',
|
||||
{
|
||||
defaultMessage: 'Headers in use',
|
||||
}
|
||||
);
|
||||
|
||||
export const KEY_LABEL = i18n.translate('xpack.stackConnectors.components.auth.keyTextFieldLabel', {
|
||||
defaultMessage: 'Key',
|
||||
});
|
||||
|
||||
export const VALUE_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.valueTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Value',
|
||||
}
|
||||
);
|
||||
|
||||
export const ADD_BUTTON = i18n.translate('xpack.stackConnectors.components.auth.addHeaderButton', {
|
||||
defaultMessage: 'Add',
|
||||
});
|
||||
|
||||
export const DELETE_BUTTON = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.deleteHeaderButton',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
description: 'Delete HTTP header',
|
||||
}
|
||||
);
|
||||
|
||||
export const CA_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.error.requiredCAText',
|
||||
{
|
||||
defaultMessage: 'CA file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ADD_CA_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.viewCertificateAuthoritySwitch',
|
||||
{
|
||||
defaultMessage: 'Add certificate authority',
|
||||
}
|
||||
);
|
||||
|
||||
export const VERIFICATION_MODE_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.verificationModeFieldLabel',
|
||||
{ defaultMessage: 'Verification mode' }
|
||||
);
|
||||
|
||||
export const EDIT_CA_CALLOUT = i18n.translate(
|
||||
'xpack.stackConnectors.components.auth.editCACallout',
|
||||
{
|
||||
defaultMessage:
|
||||
'This connector has an existing certificate authority file. Upload a new one to replace it.',
|
||||
}
|
||||
);
|
|
@ -6,25 +6,10 @@
|
|||
*/
|
||||
|
||||
import React, { FunctionComponent } from 'react';
|
||||
import {
|
||||
EuiButtonEmpty,
|
||||
EuiButtonIcon,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiTitle,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
FIELD_TYPES,
|
||||
UseArray,
|
||||
UseField,
|
||||
useFormContext,
|
||||
useFormData,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { Field, TextField, PasswordField } from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
|
||||
import * as i18n from '../translations';
|
||||
const { emptyField } = fieldValidators;
|
||||
|
||||
import { EuiSpacer } from '@elastic/eui';
|
||||
|
||||
import { AuthConfig } from '../../../common/auth/auth_config';
|
||||
|
||||
interface Props {
|
||||
display: boolean;
|
||||
|
@ -32,158 +17,10 @@ interface Props {
|
|||
}
|
||||
|
||||
export const AuthStep: FunctionComponent<Props> = ({ display, readOnly }) => {
|
||||
const { getFieldDefaultValue } = useFormContext();
|
||||
const [{ config, __internal__ }] = useFormData({
|
||||
watch: ['config.hasAuth', '__internal__.hasHeaders'],
|
||||
});
|
||||
|
||||
const hasHeadersDefaultValue = !!getFieldDefaultValue<boolean | undefined>('config.headers');
|
||||
|
||||
const hasAuth = config == null ? true : config.hasAuth;
|
||||
const hasHeaders = __internal__ != null ? __internal__.hasHeaders : false;
|
||||
|
||||
return (
|
||||
<span data-test-subj="authStep" style={{ display: display ? 'block' : 'none' }}>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxs">
|
||||
<h4>{i18n.AUTH_TITLE}</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="m" />
|
||||
<UseField
|
||||
path="config.hasAuth"
|
||||
component={Field}
|
||||
config={{ defaultValue: true, type: FIELD_TYPES.TOGGLE }}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
label: i18n.HAS_AUTH,
|
||||
disabled: readOnly,
|
||||
'data-test-subj': 'hasAuthToggle',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{hasAuth ? (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.user"
|
||||
config={{
|
||||
label: i18n.USERNAME,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.USERNAME_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={Field}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly, 'data-test-subj': 'webhookUserInput', fullWidth: true },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.password"
|
||||
config={{
|
||||
label: i18n.PASSWORD,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.PASSWORD_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={PasswordField}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly, 'data-test-subj': 'webhookPasswordInput' },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
) : null}
|
||||
<EuiSpacer size="m" />
|
||||
<UseField
|
||||
path="__internal__.hasHeaders"
|
||||
component={Field}
|
||||
config={{
|
||||
defaultValue: hasHeadersDefaultValue,
|
||||
label: i18n.HEADERS_SWITCH,
|
||||
type: FIELD_TYPES.TOGGLE,
|
||||
}}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
disabled: readOnly,
|
||||
'data-test-subj': 'webhookViewHeadersSwitch',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
{hasHeaders ? (
|
||||
<UseArray path="config.headers" initialNumberOfItems={1}>
|
||||
{({ items, addItem, removeItem }) => {
|
||||
return (
|
||||
<>
|
||||
<EuiTitle size="xxs" data-test-subj="webhookHeaderText">
|
||||
<h5>{i18n.HEADERS_TITLE}</h5>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
{items.map((item) => (
|
||||
<EuiFlexGroup key={item.id}>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path={`${item.path}.key`}
|
||||
config={{
|
||||
label: i18n.KEY_LABEL,
|
||||
}}
|
||||
component={TextField}
|
||||
// This is needed because when you delete
|
||||
// a row and add a new one, the stale values will appear
|
||||
readDefaultValueOnForm={!item.isNew}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly, ['data-test-subj']: 'webhookHeadersKeyInput' },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path={`${item.path}.value`}
|
||||
config={{ label: i18n.VALUE_LABEL }}
|
||||
component={TextField}
|
||||
readDefaultValueOnForm={!item.isNew}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
readOnly,
|
||||
['data-test-subj']: 'webhookHeadersValueInput',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
color="danger"
|
||||
onClick={() => removeItem(item.id)}
|
||||
iconType="minusInCircle"
|
||||
aria-label={i18n.DELETE_BUTTON}
|
||||
style={{ marginTop: '28px' }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButtonEmpty
|
||||
iconType="plusInCircle"
|
||||
onClick={addItem}
|
||||
data-test-subj="webhookAddHeaderButton"
|
||||
>
|
||||
{i18n.ADD_BUTTON}
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</UseArray>
|
||||
) : null}
|
||||
<AuthConfig readOnly={readOnly} hideSSL />
|
||||
<EuiSpacer size="s" />
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -112,20 +112,6 @@ export const MISSING_VARIABLES = (variables: string[]) =>
|
|||
values: { variableCount: variables.length, variables: variables.join(', ') },
|
||||
});
|
||||
|
||||
export const USERNAME_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText',
|
||||
{
|
||||
defaultMessage: 'Username is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PASSWORD_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText',
|
||||
{
|
||||
defaultMessage: 'Password is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const SUMMARY_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.error.requiredWebhookSummaryText',
|
||||
{
|
||||
|
@ -133,35 +119,6 @@ export const SUMMARY_REQUIRED = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const KEY_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Key',
|
||||
}
|
||||
);
|
||||
|
||||
export const VALUE_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Value',
|
||||
}
|
||||
);
|
||||
|
||||
export const ADD_BUTTON = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.addHeaderButton',
|
||||
{
|
||||
defaultMessage: 'Add',
|
||||
}
|
||||
);
|
||||
|
||||
export const DELETE_BUTTON = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.deleteHeaderButton',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
description: 'Delete HTTP header',
|
||||
}
|
||||
);
|
||||
|
||||
export const CREATE_INCIDENT_METHOD = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.createIncidentMethodTextFieldLabel',
|
||||
{
|
||||
|
@ -338,41 +295,6 @@ export const HAS_AUTH = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const USERNAME = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.userTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Username',
|
||||
}
|
||||
);
|
||||
|
||||
export const PASSWORD = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Password',
|
||||
}
|
||||
);
|
||||
|
||||
export const HEADERS_SWITCH = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch',
|
||||
{
|
||||
defaultMessage: 'Add HTTP header',
|
||||
}
|
||||
);
|
||||
|
||||
export const HEADERS_TITLE = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.httpHeadersTitle',
|
||||
{
|
||||
defaultMessage: 'Headers in use',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTH_TITLE = i18n.translate(
|
||||
'xpack.stackConnectors.components.casesWebhook.authenticationLabel',
|
||||
{
|
||||
defaultMessage: 'Authentication',
|
||||
}
|
||||
);
|
||||
|
||||
export const STEP_1 = i18n.translate('xpack.stackConnectors.components.casesWebhook.step1', {
|
||||
defaultMessage: 'Set up connector',
|
||||
});
|
||||
|
|
|
@ -7,8 +7,9 @@
|
|||
|
||||
import React from 'react';
|
||||
import CasesWebhookActionConnectorFields from './webhook_connectors';
|
||||
import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../lib/test_utils';
|
||||
import { act, render } from '@testing-library/react';
|
||||
import { ConnectorFormTestProvider } from '../lib/test_utils';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { AuthType } from '../../../common/auth/constants';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import * as i18n from './translations';
|
||||
|
||||
|
@ -27,6 +28,7 @@ jest.mock('@kbn/triggers-actions-ui-plugin/public', () => {
|
|||
const invalidJsonTitle = `{"fields":{"summary":"wrong","description":{{{case.description}}},"project":{"key":"ROC"},"issuetype":{"id":"10024"}}}`;
|
||||
const invalidJsonBoth = `{"fields":{"summary":"wrong","description":"wrong","project":{"key":"ROC"},"issuetype":{"id":"10024"}}}`;
|
||||
const config = {
|
||||
authType: AuthType.Basic,
|
||||
createCommentJson: '{"body":{{{case.comment}}}}',
|
||||
createCommentMethod: 'post',
|
||||
createCommentUrl: 'https://coolsite.net/rest/api/2/issue/{{{external.system.id}}}/comment',
|
||||
|
@ -59,8 +61,8 @@ const actionConnector = {
|
|||
};
|
||||
|
||||
describe('CasesWebhookActionConnectorFields renders', () => {
|
||||
test('All inputs are properly rendered', async () => {
|
||||
const { getByTestId } = render(
|
||||
it('All inputs are properly rendered', async () => {
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={actionConnector}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -69,55 +71,70 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
await waitForComponentToUpdate();
|
||||
expect(getByTestId('webhookUserInput')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookPasswordInput')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookHeadersKeyInput')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookHeadersValueInput')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookCreateMethodSelect')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookCreateUrlText')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookCreateIncidentJson')).toBeInTheDocument();
|
||||
expect(getByTestId('createIncidentResponseKeyText')).toBeInTheDocument();
|
||||
expect(getByTestId('getIncidentUrlInput')).toBeInTheDocument();
|
||||
expect(getByTestId('getIncidentResponseExternalTitleKeyText')).toBeInTheDocument();
|
||||
expect(getByTestId('viewIncidentUrlInput')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookUpdateMethodSelect')).toBeInTheDocument();
|
||||
expect(getByTestId('updateIncidentUrlInput')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookUpdateIncidentJson')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookCreateCommentMethodSelect')).toBeInTheDocument();
|
||||
expect(getByTestId('createCommentUrlInput')).toBeInTheDocument();
|
||||
expect(getByTestId('webhookCreateCommentJson')).toBeInTheDocument();
|
||||
});
|
||||
test('Toggles work properly', async () => {
|
||||
const { getByTestId, queryByTestId } = render(
|
||||
<ConnectorFormTestProvider connector={actionConnector}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
isEdit={false}
|
||||
registerPreSubmitValidator={() => {}}
|
||||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
await waitForComponentToUpdate();
|
||||
expect(getByTestId('hasAuthToggle')).toHaveAttribute('aria-checked', 'true');
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('hasAuthToggle'));
|
||||
});
|
||||
expect(getByTestId('hasAuthToggle')).toHaveAttribute('aria-checked', 'false');
|
||||
expect(queryByTestId('webhookUserInput')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('webhookPasswordInput')).not.toBeInTheDocument();
|
||||
|
||||
expect(getByTestId('webhookViewHeadersSwitch')).toHaveAttribute('aria-checked', 'true');
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('webhookViewHeadersSwitch'));
|
||||
});
|
||||
expect(getByTestId('webhookViewHeadersSwitch')).toHaveAttribute('aria-checked', 'false');
|
||||
expect(queryByTestId('webhookHeadersKeyInput')).not.toBeInTheDocument();
|
||||
expect(queryByTestId('webhookHeadersValueInput')).not.toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authNone')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authBasic')).toBeInTheDocument();
|
||||
// expect(await screen.findByTestId('authSSL')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookUserInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookPasswordInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookHeadersKeyInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookHeadersValueInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCreateMethodSelect')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCreateUrlText')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCreateIncidentJson')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('createIncidentResponseKeyText')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('getIncidentUrlInput')).toBeInTheDocument();
|
||||
expect(
|
||||
await screen.findByTestId('getIncidentResponseExternalTitleKeyText')
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('viewIncidentUrlInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookUpdateMethodSelect')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('updateIncidentUrlInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookUpdateIncidentJson')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCreateCommentMethodSelect')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('createCommentUrlInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookCreateCommentJson')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('connector auth toggles work as expected', async () => {
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={actionConnector}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
isEdit={false}
|
||||
registerPreSubmitValidator={() => {}}
|
||||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
const authNoneToggle = await screen.findByTestId('authNone');
|
||||
|
||||
expect(authNoneToggle).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authBasic')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookUserInput')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('webhookPasswordInput')).toBeInTheDocument();
|
||||
|
||||
userEvent.click(authNoneToggle);
|
||||
|
||||
expect(screen.queryByTestId('webhookUserInput')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('webhookPasswordInput')).not.toBeInTheDocument();
|
||||
|
||||
expect(await screen.findByTestId('webhookViewHeadersSwitch')).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'true'
|
||||
);
|
||||
userEvent.click(await screen.findByTestId('webhookViewHeadersSwitch'));
|
||||
expect(await screen.findByTestId('webhookViewHeadersSwitch')).toHaveAttribute(
|
||||
'aria-checked',
|
||||
'false'
|
||||
);
|
||||
expect(screen.queryByTestId('webhookHeadersKeyInput')).not.toBeInTheDocument();
|
||||
expect(screen.queryByTestId('webhookHeadersValueInput')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
describe('Step Validation', () => {
|
||||
test('Steps work correctly when all fields valid', async () => {
|
||||
const { queryByTestId, getByTestId } = render(
|
||||
it('Steps work correctly when all fields valid', async () => {
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={actionConnector}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -126,52 +143,52 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
await waitForComponentToUpdate();
|
||||
expect(getByTestId('horizontalStep1-current')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep2-incomplete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep3-incomplete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
expect(getByTestId('authStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(getByTestId('createStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('getStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(queryByTestId('casesWebhookBack')).not.toBeInTheDocument();
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep2-current')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep3-incomplete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
expect(getByTestId('authStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('createStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(getByTestId('getStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: none;');
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep2-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep3-current')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
expect(getByTestId('authStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('createStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('getStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: none;');
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep2-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep3-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep4-current')).toBeInTheDocument();
|
||||
expect(getByTestId('authStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('createStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('getStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(getByTestId('updateStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(queryByTestId('casesWebhookNext')).not.toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep1-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep3-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(screen.queryByTestId('casesWebhookBack')).not.toBeInTheDocument();
|
||||
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep3-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: none;');
|
||||
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep3-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: none;');
|
||||
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep3-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep4-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('authStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('createStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('getStep')).toHaveAttribute('style', 'display: none;');
|
||||
expect(await screen.findByTestId('updateStep')).toHaveAttribute('style', 'display: block;');
|
||||
expect(screen.queryByTestId('casesWebhookNext')).not.toBeInTheDocument();
|
||||
});
|
||||
test('Step 1 is properly validated', async () => {
|
||||
|
||||
it('Step 1 is properly validated', async () => {
|
||||
const incompleteActionConnector = {
|
||||
...actionConnector,
|
||||
secrets: {
|
||||
|
@ -179,7 +196,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
password: '',
|
||||
},
|
||||
};
|
||||
const { getByTestId } = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={incompleteActionConnector}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -188,29 +205,22 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
await waitForComponentToUpdate();
|
||||
|
||||
expect(getByTestId('horizontalStep1-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep1-current')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
await waitForComponentToUpdate();
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(getByTestId('horizontalStep1-danger')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep1-danger')).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('hasAuthToggle'));
|
||||
userEvent.click(getByTestId('webhookViewHeadersSwitch'));
|
||||
});
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('authNone'));
|
||||
userEvent.click(await screen.findByTestId('webhookViewHeadersSwitch'));
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(getByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep2-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep1-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-current')).toBeInTheDocument();
|
||||
});
|
||||
test('Step 2 is properly validated', async () => {
|
||||
|
||||
it('Step 2 is properly validated', async () => {
|
||||
const incompleteActionConnector = {
|
||||
...actionConnector,
|
||||
config: {
|
||||
|
@ -218,7 +228,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
createIncidentUrl: undefined,
|
||||
},
|
||||
};
|
||||
const { getByText, getByTestId } = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={incompleteActionConnector}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -227,37 +237,31 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
await waitForComponentToUpdate();
|
||||
expect(getByTestId('horizontalStep2-incomplete')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
getByText(i18n.CREATE_URL_REQUIRED);
|
||||
expect(getByTestId('horizontalStep2-danger')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
await userEvent.type(
|
||||
getByTestId('webhookCreateUrlText'),
|
||||
`{selectall}{backspace}${config.createIncidentUrl}`,
|
||||
{
|
||||
delay: 10,
|
||||
}
|
||||
);
|
||||
});
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
expect(getByTestId('horizontalStep2-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep3-current')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('horizontalStep2-complete'));
|
||||
});
|
||||
expect(getByTestId('horizontalStep2-current')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep3-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-incomplete')).toBeInTheDocument();
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
expect(await screen.findByText(i18n.CREATE_URL_REQUIRED)).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-danger')).toBeInTheDocument();
|
||||
await userEvent.type(
|
||||
await screen.findByTestId('webhookCreateUrlText'),
|
||||
`{selectall}{backspace}${config.createIncidentUrl}`,
|
||||
{
|
||||
delay: 10,
|
||||
}
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(await screen.findByTestId('horizontalStep2-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep3-current')).toBeInTheDocument();
|
||||
|
||||
userEvent.click(await screen.findByTestId('horizontalStep2-complete'));
|
||||
|
||||
expect(await screen.findByTestId('horizontalStep2-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep3-incomplete')).toBeInTheDocument();
|
||||
});
|
||||
test('Step 3 is properly validated', async () => {
|
||||
|
||||
it('Step 3 is properly validated', async () => {
|
||||
const incompleteActionConnector = {
|
||||
...actionConnector,
|
||||
config: {
|
||||
|
@ -265,7 +269,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
getIncidentResponseExternalTitleKey: undefined,
|
||||
},
|
||||
};
|
||||
const { getByText, getByTestId } = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={incompleteActionConnector}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -274,38 +278,34 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
/>
|
||||
</ConnectorFormTestProvider>
|
||||
);
|
||||
await waitForComponentToUpdate();
|
||||
expect(getByTestId('horizontalStep2-incomplete')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
getByText(i18n.GET_RESPONSE_EXTERNAL_TITLE_KEY_REQUIRED);
|
||||
expect(getByTestId('horizontalStep3-danger')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
await userEvent.type(
|
||||
getByTestId('getIncidentResponseExternalTitleKeyText'),
|
||||
`{selectall}{backspace}${config.getIncidentResponseExternalTitleKey}`,
|
||||
{
|
||||
delay: 10,
|
||||
}
|
||||
);
|
||||
});
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('casesWebhookNext'));
|
||||
});
|
||||
expect(getByTestId('horizontalStep3-complete')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep4-current')).toBeInTheDocument();
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('horizontalStep3-complete'));
|
||||
});
|
||||
expect(getByTestId('horizontalStep3-current')).toBeInTheDocument();
|
||||
expect(getByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep2-incomplete')).toBeInTheDocument();
|
||||
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(
|
||||
await screen.findByText(i18n.GET_RESPONSE_EXTERNAL_TITLE_KEY_REQUIRED)
|
||||
).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep3-danger')).toBeInTheDocument();
|
||||
|
||||
await userEvent.type(
|
||||
await screen.findByTestId('getIncidentResponseExternalTitleKeyText'),
|
||||
`{selectall}{backspace}${config.getIncidentResponseExternalTitleKey}`,
|
||||
{
|
||||
delay: 10,
|
||||
}
|
||||
);
|
||||
|
||||
userEvent.click(await screen.findByTestId('casesWebhookNext'));
|
||||
|
||||
expect(await screen.findByTestId('horizontalStep3-complete')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep4-current')).toBeInTheDocument();
|
||||
|
||||
userEvent.click(await screen.findByTestId('horizontalStep3-complete'));
|
||||
|
||||
expect(await screen.findByTestId('horizontalStep3-current')).toBeInTheDocument();
|
||||
expect(await screen.findByTestId('horizontalStep4-incomplete')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
// step 4 is not validated like the others since it is the last step
|
||||
|
@ -346,7 +346,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
];
|
||||
|
||||
it('connector validation succeeds when connector config is valid', async () => {
|
||||
const { getByTestId } = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={actionConnector} onSubmit={onSubmit}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -356,20 +356,20 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
const { isPreconfigured, ...rest } = actionConnector;
|
||||
|
||||
expect(onSubmit).toBeCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
await waitFor(() =>
|
||||
expect(onSubmit).toBeCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
__internal__: {
|
||||
// hasCA: false,
|
||||
hasHeaders: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
isValid: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('connector validation succeeds when auth=false', async () => {
|
||||
|
@ -381,7 +381,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={connector} onSubmit={onSubmit}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -391,24 +391,26 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
const { isPreconfigured, secrets, ...rest } = actionConnector;
|
||||
expect(onSubmit).toBeCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
config: {
|
||||
...actionConnector.config,
|
||||
hasAuth: false,
|
||||
await waitFor(() =>
|
||||
expect(onSubmit).toBeCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
config: {
|
||||
...actionConnector.config,
|
||||
hasAuth: false,
|
||||
authType: null,
|
||||
},
|
||||
__internal__: {
|
||||
// hasCA: false,
|
||||
hasHeaders: true,
|
||||
},
|
||||
},
|
||||
__internal__: {
|
||||
hasHeaders: true,
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
isValid: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('connector validation succeeds without headers', async () => {
|
||||
|
@ -420,7 +422,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const { getByTestId } = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={connector} onSubmit={onSubmit}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -430,22 +432,23 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
const { isPreconfigured, ...rest } = actionConnector;
|
||||
const { headers, ...rest2 } = actionConnector.config;
|
||||
expect(onSubmit).toBeCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
config: rest2,
|
||||
__internal__: {
|
||||
hasHeaders: false,
|
||||
await waitFor(() =>
|
||||
expect(onSubmit).toBeCalledWith({
|
||||
data: {
|
||||
...rest,
|
||||
config: rest2,
|
||||
__internal__: {
|
||||
// hasCA: false,
|
||||
hasHeaders: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
isValid: true,
|
||||
});
|
||||
isValid: true,
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('validates correctly if the method is empty', async () => {
|
||||
|
@ -457,7 +460,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const res = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={connector} onSubmit={onSubmit}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -467,11 +470,8 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(res.getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false });
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }));
|
||||
});
|
||||
|
||||
it.each(tests)('validates correctly %p', async (field, value) => {
|
||||
|
@ -483,7 +483,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const res = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={connector} onSubmit={onSubmit}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -493,17 +493,13 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
await userEvent.type(res.getByTestId(field), `{selectall}{backspace}${value}`, {
|
||||
delay: 10,
|
||||
});
|
||||
await userEvent.type(await screen.findByTestId(field), `{selectall}{backspace}${value}`, {
|
||||
delay: 10,
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(res.getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false });
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }));
|
||||
});
|
||||
|
||||
it.each(mustacheTests)(
|
||||
|
@ -518,7 +514,7 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const res = render(
|
||||
render(
|
||||
<ConnectorFormTestProvider connector={connector} onSubmit={onSubmit}>
|
||||
<CasesWebhookActionConnectorFields
|
||||
readOnly={false}
|
||||
|
@ -528,12 +524,11 @@ describe('CasesWebhookActionConnectorFields renders', () => {
|
|||
</ConnectorFormTestProvider>
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(res.getByTestId('form-test-provide-submit'));
|
||||
});
|
||||
|
||||
expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false });
|
||||
expect(res.getByText(i18n.MISSING_VARIABLES(missingVariables))).toBeInTheDocument();
|
||||
userEvent.click(await screen.findByTestId('form-test-provide-submit'));
|
||||
await waitFor(() => expect(onSubmit).toHaveBeenCalledWith({ data: {}, isValid: false }));
|
||||
expect(
|
||||
await screen.findByText(i18n.MISSING_VARIABLES(missingVariables))
|
||||
).toBeInTheDocument();
|
||||
}
|
||||
);
|
||||
});
|
||||
|
|
|
@ -52,6 +52,21 @@ const ConnectorFormTestProviderComponent: React.FC<ConnectorFormTestProviderProp
|
|||
ConnectorFormTestProviderComponent.displayName = 'ConnectorFormTestProvider';
|
||||
export const ConnectorFormTestProvider = React.memo(ConnectorFormTestProviderComponent);
|
||||
|
||||
const AuthFormTestProviderComponent: React.FC<FormTestProviderProps> = ({
|
||||
children,
|
||||
defaultValue,
|
||||
onSubmit,
|
||||
}) => {
|
||||
return (
|
||||
<FormTestProviderComponent onSubmit={onSubmit} defaultValue={defaultValue}>
|
||||
{children}
|
||||
</FormTestProviderComponent>
|
||||
);
|
||||
};
|
||||
|
||||
AuthFormTestProviderComponent.displayName = 'AuthFormTestProvider';
|
||||
export const AuthFormTestProvider = React.memo(AuthFormTestProviderComponent);
|
||||
|
||||
const FormTestProviderComponent: React.FC<FormTestProviderProps> = ({
|
||||
children,
|
||||
defaultValue,
|
||||
|
|
|
@ -14,13 +14,6 @@ export const METHOD_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const HAS_AUTH_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.hasAuthSwitchLabel',
|
||||
{
|
||||
defaultMessage: 'Require authentication for this webhook',
|
||||
}
|
||||
);
|
||||
|
||||
export const URL_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.urlTextFieldLabel',
|
||||
{
|
||||
|
@ -28,62 +21,6 @@ export const URL_LABEL = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const USERNAME_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.userTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Username',
|
||||
}
|
||||
);
|
||||
|
||||
export const PASSWORD_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.passwordTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Password',
|
||||
}
|
||||
);
|
||||
|
||||
export const PASSPHRASE_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.passphraseTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Passphrase',
|
||||
}
|
||||
);
|
||||
|
||||
export const ADD_HEADERS_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.viewHeadersSwitch',
|
||||
{
|
||||
defaultMessage: 'Add HTTP header',
|
||||
}
|
||||
);
|
||||
|
||||
export const HEADER_KEY_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Key',
|
||||
}
|
||||
);
|
||||
|
||||
export const REMOVE_ITEM_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.removeHeaderIconLabel',
|
||||
{
|
||||
defaultMessage: 'Key',
|
||||
}
|
||||
);
|
||||
|
||||
export const ADD_HEADER_BTN = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.addHeaderButtonLabel',
|
||||
{
|
||||
defaultMessage: 'Add header',
|
||||
}
|
||||
);
|
||||
|
||||
export const HEADER_VALUE_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.headerValueTextFieldLabel',
|
||||
{
|
||||
defaultMessage: 'Value',
|
||||
}
|
||||
);
|
||||
|
||||
export const URL_INVALID = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.invalidUrlTextField',
|
||||
{
|
||||
|
@ -98,105 +35,9 @@ export const METHOD_REQUIRED = i18n.translate(
|
|||
}
|
||||
);
|
||||
|
||||
export const USERNAME_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText',
|
||||
{
|
||||
defaultMessage: 'Username is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const BODY_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText',
|
||||
{
|
||||
defaultMessage: 'Body is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PASSWORD_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText',
|
||||
{
|
||||
defaultMessage: 'Password is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_NONE = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel',
|
||||
{
|
||||
defaultMessage: 'None',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_BASIC = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel',
|
||||
{
|
||||
defaultMessage: 'Basic authentication',
|
||||
}
|
||||
);
|
||||
|
||||
export const AUTHENTICATION_SSL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel',
|
||||
{
|
||||
defaultMessage: 'SSL authentication',
|
||||
}
|
||||
);
|
||||
|
||||
export const CERT_TYPE_CRT_KEY = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel',
|
||||
{
|
||||
defaultMessage: 'CRT and KEY file',
|
||||
}
|
||||
);
|
||||
export const CERT_TYPE_PFX = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.certTypePfxLabel',
|
||||
{
|
||||
defaultMessage: 'PFX file',
|
||||
}
|
||||
);
|
||||
|
||||
export const CRT_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText',
|
||||
{
|
||||
defaultMessage: 'CRT file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const KEY_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText',
|
||||
{
|
||||
defaultMessage: 'KEY file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const PFX_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText',
|
||||
{
|
||||
defaultMessage: 'PFX file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const CA_REQUIRED = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.error.requiredWebhookCAText',
|
||||
{
|
||||
defaultMessage: 'CA file is required.',
|
||||
}
|
||||
);
|
||||
|
||||
export const ADD_CA_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch',
|
||||
{
|
||||
defaultMessage: 'Add certificate authority',
|
||||
}
|
||||
);
|
||||
|
||||
export const VERIFICATION_MODE_LABEL = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.verificationModeFieldLabel',
|
||||
{ defaultMessage: 'Verification mode' }
|
||||
);
|
||||
|
||||
export const EDIT_CA_CALLOUT = i18n.translate(
|
||||
'xpack.stackConnectors.components.webhook.editCACallout',
|
||||
{
|
||||
defaultMessage:
|
||||
'This webhook has an existing certificate authority file. Upload a new one to replace it.',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -11,10 +11,10 @@ import WebhookActionConnectorFields from './webhook_connectors';
|
|||
import { ConnectorFormTestProvider, waitForComponentToUpdate } from '../lib/test_utils';
|
||||
import { act, render } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { WebhookAuthType, SSLCertType } from '../../../common/webhook/constants';
|
||||
import { AuthType, SSLCertType } from '../../../common/auth/constants';
|
||||
|
||||
describe('WebhookActionConnectorFields renders', () => {
|
||||
test('all connector fields is rendered', async () => {
|
||||
it('renders all connector fields', async () => {
|
||||
const actionConnector = {
|
||||
actionTypeId: '.webhook',
|
||||
name: 'webhook',
|
||||
|
@ -23,7 +23,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
url: 'https://test.com',
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
hasAuth: true,
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
|
@ -104,7 +104,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
url: 'https://test.com',
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
hasAuth: true,
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
|
@ -171,7 +171,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
method: 'PUT',
|
||||
url: 'https://test.com',
|
||||
hasAuth: true,
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -197,7 +197,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
method: 'PUT',
|
||||
url: 'https://test.com',
|
||||
hasAuth: true,
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
|
@ -303,7 +303,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
method: 'PUT',
|
||||
url: 'https://test.com',
|
||||
hasAuth: true,
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
ca: Buffer.from('some binary string').toString('base64'),
|
||||
verificationMode: 'full',
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
|
@ -327,7 +327,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
...actionConnector,
|
||||
config: {
|
||||
...actionConnector.config,
|
||||
authType: WebhookAuthType.SSL,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
|
@ -358,7 +358,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
method: 'PUT',
|
||||
url: 'https://test.com',
|
||||
hasAuth: true,
|
||||
authType: WebhookAuthType.SSL,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.CRT,
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
},
|
||||
|
@ -381,7 +381,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
...actionConnector,
|
||||
config: {
|
||||
...actionConnector.config,
|
||||
authType: WebhookAuthType.SSL,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.PFX,
|
||||
},
|
||||
secrets: {
|
||||
|
@ -411,7 +411,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
method: 'PUT',
|
||||
url: 'https://test.com',
|
||||
hasAuth: true,
|
||||
authType: WebhookAuthType.SSL,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.PFX,
|
||||
headers: [{ key: 'content-type', value: 'text' }],
|
||||
},
|
||||
|
@ -433,7 +433,7 @@ describe('WebhookActionConnectorFields renders', () => {
|
|||
...actionConnector,
|
||||
config: {
|
||||
...actionConnector.config,
|
||||
authType: WebhookAuthType.SSL,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.CRT,
|
||||
},
|
||||
secrets: {
|
||||
|
|
|
@ -5,237 +5,25 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useEffect } from 'react';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiSpacer,
|
||||
EuiButtonIcon,
|
||||
EuiTitle,
|
||||
EuiButtonEmpty,
|
||||
EuiCallOut,
|
||||
EuiTabs,
|
||||
EuiTab,
|
||||
} from '@elastic/eui';
|
||||
import {
|
||||
UseArray,
|
||||
UseField,
|
||||
useFormContext,
|
||||
useFormData,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import {
|
||||
Field,
|
||||
SelectField,
|
||||
TextField,
|
||||
ToggleField,
|
||||
PasswordField,
|
||||
FilePickerField,
|
||||
CardRadioGroupField,
|
||||
HiddenField,
|
||||
} from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui';
|
||||
import { UseField } from '@kbn/es-ui-shared-plugin/static/forms/hook_form_lib';
|
||||
import { Field, SelectField } from '@kbn/es-ui-shared-plugin/static/forms/components';
|
||||
import { fieldValidators } from '@kbn/es-ui-shared-plugin/static/forms/helpers';
|
||||
import type { ActionConnectorFieldsProps } from '@kbn/triggers-actions-ui-plugin/public';
|
||||
import { WebhookAuthType, SSLCertType } from '../../../common/webhook/constants';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { AuthConfig } from '../../common/auth/auth_config';
|
||||
|
||||
const HTTP_VERBS = ['post', 'put'];
|
||||
const { emptyField, urlField } = fieldValidators;
|
||||
const VERIFICATION_MODE_DEFAULT = 'full';
|
||||
|
||||
const WebhookActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsProps> = ({
|
||||
readOnly,
|
||||
}) => {
|
||||
const { setFieldValue, getFieldDefaultValue } = useFormContext();
|
||||
const [{ config, __internal__ }] = useFormData({
|
||||
watch: [
|
||||
'config.hasAuth',
|
||||
'config.authType',
|
||||
'config.certType',
|
||||
'config.verificationMode',
|
||||
'__internal__.hasHeaders',
|
||||
'__internal__.hasCA',
|
||||
],
|
||||
});
|
||||
|
||||
const hasHeadersDefaultValue = !!getFieldDefaultValue<boolean | undefined>('config.headers');
|
||||
const authTypeDefaultValue =
|
||||
getFieldDefaultValue('config.hasAuth') === false
|
||||
? null
|
||||
: getFieldDefaultValue('config.authType') ?? WebhookAuthType.Basic;
|
||||
const certTypeDefaultValue = getFieldDefaultValue('config.certType') ?? SSLCertType.CRT;
|
||||
const hasCADefaultValue =
|
||||
!!getFieldDefaultValue<boolean | undefined>('config.ca') ||
|
||||
getFieldDefaultValue('config.verificationMode') === 'none';
|
||||
|
||||
const hasHeaders = __internal__ != null ? __internal__.hasHeaders : false;
|
||||
const hasCA = __internal__ != null ? __internal__.hasCA : false;
|
||||
const authType = config == null ? WebhookAuthType.Basic : config.authType;
|
||||
const certType = config == null ? SSLCertType.CRT : config.certType;
|
||||
|
||||
const hasInitialCA = !!getFieldDefaultValue<boolean | undefined>('config.ca');
|
||||
|
||||
useEffect(() => setFieldValue('config.hasAuth', Boolean(authType)), [authType, setFieldValue]);
|
||||
|
||||
const basicAuthFields = (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.user"
|
||||
config={{
|
||||
label: i18n.USERNAME_LABEL,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.USERNAME_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={Field}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
readOnly,
|
||||
'data-test-subj': 'webhookUserInput',
|
||||
fullWidth: true,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.password"
|
||||
config={{
|
||||
label: i18n.PASSWORD_LABEL,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.PASSWORD_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={PasswordField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookPasswordInput',
|
||||
readOnly,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
const sslCertAuthFields = (
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.password"
|
||||
config={{
|
||||
label: i18n.PASSPHRASE_LABEL,
|
||||
}}
|
||||
component={PasswordField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLPassphraseInput',
|
||||
readOnly,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
<UseField
|
||||
path="config.certType"
|
||||
defaultValue={certTypeDefaultValue}
|
||||
component={({ field }) => (
|
||||
<EuiTabs size="s">
|
||||
<EuiTab
|
||||
onClick={() => field.setValue(SSLCertType.CRT)}
|
||||
isSelected={field.value === SSLCertType.CRT}
|
||||
>
|
||||
{i18n.CERT_TYPE_CRT_KEY}
|
||||
</EuiTab>
|
||||
<EuiTab
|
||||
onClick={() => field.setValue(SSLCertType.PFX)}
|
||||
isSelected={field.value === SSLCertType.PFX}
|
||||
>
|
||||
{i18n.CERT_TYPE_PFX}
|
||||
</EuiTab>
|
||||
</EuiTabs>
|
||||
)}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
{certType === SSLCertType.CRT && (
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.crt"
|
||||
config={{
|
||||
label: 'CRT file',
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.CRT_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLCRTInput',
|
||||
display: 'default',
|
||||
accept: '.crt,.cert,.cer,.pem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="secrets.key"
|
||||
config={{
|
||||
label: 'KEY file',
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.KEY_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLKEYInput',
|
||||
display: 'default',
|
||||
accept: '.key,.pem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)}
|
||||
{certType === SSLCertType.PFX && (
|
||||
<UseField
|
||||
path="secrets.pfx"
|
||||
config={{
|
||||
label: 'PFX file',
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.PFX_REQUIRED),
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookSSLPFXInput',
|
||||
display: 'default',
|
||||
accept: '.pfx,.p12',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<UseField path="config.hasAuth" component={HiddenField} />
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem grow={false}>
|
||||
<UseField
|
||||
|
@ -278,189 +66,8 @@ const WebhookActionConnectorFields: React.FunctionComponent<ActionConnectorField
|
|||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiTitle size="xxs">
|
||||
<h4>
|
||||
<FormattedMessage
|
||||
id="xpack.stackConnectors.components.webhook.authenticationLabel"
|
||||
defaultMessage="Authentication"
|
||||
/>
|
||||
</h4>
|
||||
</EuiTitle>
|
||||
<EuiSpacer size="s" />
|
||||
<UseField
|
||||
path="config.authType"
|
||||
defaultValue={authTypeDefaultValue}
|
||||
component={CardRadioGroupField}
|
||||
componentProps={{
|
||||
options: [
|
||||
{
|
||||
value: null,
|
||||
label: i18n.AUTHENTICATION_NONE,
|
||||
},
|
||||
{
|
||||
value: WebhookAuthType.Basic,
|
||||
label: i18n.AUTHENTICATION_BASIC,
|
||||
children: authType === WebhookAuthType.Basic && basicAuthFields,
|
||||
},
|
||||
{
|
||||
value: WebhookAuthType.SSL,
|
||||
label: i18n.AUTHENTICATION_SSL,
|
||||
children: authType === WebhookAuthType.SSL && sslCertAuthFields,
|
||||
},
|
||||
],
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiSpacer size="m" />
|
||||
<UseField
|
||||
path="__internal__.hasHeaders"
|
||||
component={ToggleField}
|
||||
config={{ defaultValue: hasHeadersDefaultValue, label: i18n.ADD_HEADERS_LABEL }}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
disabled: readOnly,
|
||||
'data-test-subj': 'webhookViewHeadersSwitch',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
{hasHeaders ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<UseArray path="config.headers" initialNumberOfItems={1}>
|
||||
{({ items, addItem, removeItem }) => {
|
||||
return (
|
||||
<>
|
||||
{items.map((item) => (
|
||||
<EuiFlexGroup key={item.id}>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path={`${item.path}.key`}
|
||||
config={{
|
||||
label: i18n.HEADER_KEY_LABEL,
|
||||
}}
|
||||
component={TextField}
|
||||
// This is needed because when you delete
|
||||
// a row and add a new one, the stale values will appear
|
||||
readDefaultValueOnForm={!item.isNew}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path={`${item.path}.value`}
|
||||
config={{ label: i18n.HEADER_VALUE_LABEL }}
|
||||
component={TextField}
|
||||
readDefaultValueOnForm={!item.isNew}
|
||||
componentProps={{
|
||||
euiFieldProps: { readOnly },
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonIcon
|
||||
color="danger"
|
||||
onClick={() => removeItem(item.id)}
|
||||
iconType="minusInCircle"
|
||||
aria-label={i18n.REMOVE_ITEM_LABEL}
|
||||
style={{ marginTop: '28px' }}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
))}
|
||||
<EuiSpacer size="m" />
|
||||
<EuiButtonEmpty iconType="plusInCircle" onClick={addItem}>
|
||||
{i18n.ADD_HEADER_BTN}
|
||||
</EuiButtonEmpty>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</UseArray>
|
||||
</>
|
||||
) : null}
|
||||
<EuiSpacer size="m" />
|
||||
<UseField
|
||||
path="__internal__.hasCA"
|
||||
component={ToggleField}
|
||||
config={{ defaultValue: hasCADefaultValue, label: i18n.ADD_CA_LABEL }}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
disabled: readOnly,
|
||||
'data-test-subj': 'webhookViewCASwitch',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<EuiSpacer size="m" />
|
||||
{hasCA && (
|
||||
<>
|
||||
<EuiFlexGroup justifyContent="spaceBetween">
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="config.ca"
|
||||
config={{
|
||||
label: 'CA file',
|
||||
validations: [
|
||||
{
|
||||
validator:
|
||||
config?.verificationMode !== 'none'
|
||||
? emptyField(i18n.CA_REQUIRED)
|
||||
: () => {},
|
||||
},
|
||||
],
|
||||
}}
|
||||
component={FilePickerField}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
display: 'default',
|
||||
'data-test-subj': 'webhookCAInput',
|
||||
accept: '.ca,.pem',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<UseField
|
||||
path="config.verificationMode"
|
||||
component={SelectField}
|
||||
config={{
|
||||
label: i18n.VERIFICATION_MODE_LABEL,
|
||||
defaultValue: VERIFICATION_MODE_DEFAULT,
|
||||
validations: [
|
||||
{
|
||||
validator: emptyField(i18n.VERIFICATION_MODE_LABEL),
|
||||
},
|
||||
],
|
||||
}}
|
||||
componentProps={{
|
||||
euiFieldProps: {
|
||||
'data-test-subj': 'webhookVerificationModeSelect',
|
||||
options: [
|
||||
{ text: 'None', value: 'none' },
|
||||
{ text: 'Certificate', value: 'certificate' },
|
||||
{ text: 'Full', value: 'full' },
|
||||
],
|
||||
fullWidth: true,
|
||||
readOnly,
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
{hasInitialCA && (
|
||||
<>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiCallOut size="s" iconType="document" title={i18n.EDIT_CA_CALLOUT} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
<AuthConfig readOnly={readOnly} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -26,12 +26,13 @@ import {
|
|||
ExternalIncidentServiceSecretConfigurationSchema,
|
||||
} from './schema';
|
||||
import { api } from './api';
|
||||
import { validate } from './validators';
|
||||
import { validateCasesWebhookConfig, validateConnector } from './validators';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const supportedSubActions: string[] = ['pushToService'];
|
||||
export type ActionParamsType = CasesWebhookActionParamsType;
|
||||
export const ConnectorTypeId = '.cases-webhook';
|
||||
|
||||
// connector type definition
|
||||
export function getConnectorType(): ConnectorType<
|
||||
CasesWebhookPublicConfigurationType,
|
||||
|
@ -46,16 +47,15 @@ export function getConnectorType(): ConnectorType<
|
|||
validate: {
|
||||
config: {
|
||||
schema: ExternalIncidentServiceConfigurationSchema,
|
||||
customValidator: validate.config,
|
||||
customValidator: validateCasesWebhookConfig,
|
||||
},
|
||||
secrets: {
|
||||
schema: ExternalIncidentServiceSecretConfigurationSchema,
|
||||
customValidator: validate.secrets,
|
||||
},
|
||||
params: {
|
||||
schema: ExecutorParamsSchema,
|
||||
},
|
||||
connector: validate.connector,
|
||||
connector: validateConnector,
|
||||
},
|
||||
executor,
|
||||
supportedFeatureIds: [CasesConnectorFeatureId],
|
||||
|
|
|
@ -6,17 +6,17 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { CasesWebhookMethods } from './types';
|
||||
import { nullableType } from '../lib/nullable';
|
||||
import { WebhookMethods } from '../../../common/auth/constants';
|
||||
import { AuthConfiguration, SecretConfigurationSchema } from '../../../common/auth/schema';
|
||||
|
||||
const HeadersSchema = schema.recordOf(schema.string(), schema.string());
|
||||
|
||||
export const ExternalIncidentServiceConfiguration = {
|
||||
createIncidentUrl: schema.string(),
|
||||
createIncidentMethod: schema.oneOf(
|
||||
[schema.literal(CasesWebhookMethods.POST), schema.literal(CasesWebhookMethods.PUT)],
|
||||
[schema.literal(WebhookMethods.POST), schema.literal(WebhookMethods.PUT)],
|
||||
{
|
||||
defaultValue: CasesWebhookMethods.POST,
|
||||
defaultValue: WebhookMethods.POST,
|
||||
}
|
||||
),
|
||||
createIncidentJson: schema.string(), // stringified object
|
||||
|
@ -27,12 +27,12 @@ export const ExternalIncidentServiceConfiguration = {
|
|||
updateIncidentUrl: schema.string(),
|
||||
updateIncidentMethod: schema.oneOf(
|
||||
[
|
||||
schema.literal(CasesWebhookMethods.POST),
|
||||
schema.literal(CasesWebhookMethods.PATCH),
|
||||
schema.literal(CasesWebhookMethods.PUT),
|
||||
schema.literal(WebhookMethods.POST),
|
||||
schema.literal(WebhookMethods.PATCH),
|
||||
schema.literal(WebhookMethods.PUT),
|
||||
],
|
||||
{
|
||||
defaultValue: CasesWebhookMethods.PUT,
|
||||
defaultValue: WebhookMethods.PUT,
|
||||
}
|
||||
),
|
||||
updateIncidentJson: schema.string(),
|
||||
|
@ -40,33 +40,28 @@ export const ExternalIncidentServiceConfiguration = {
|
|||
createCommentMethod: schema.nullable(
|
||||
schema.oneOf(
|
||||
[
|
||||
schema.literal(CasesWebhookMethods.POST),
|
||||
schema.literal(CasesWebhookMethods.PUT),
|
||||
schema.literal(CasesWebhookMethods.PATCH),
|
||||
schema.literal(WebhookMethods.POST),
|
||||
schema.literal(WebhookMethods.PUT),
|
||||
schema.literal(WebhookMethods.PATCH),
|
||||
],
|
||||
{
|
||||
defaultValue: CasesWebhookMethods.PUT,
|
||||
defaultValue: WebhookMethods.PUT,
|
||||
}
|
||||
)
|
||||
),
|
||||
createCommentJson: schema.nullable(schema.string()),
|
||||
headers: nullableType(HeadersSchema),
|
||||
hasAuth: schema.boolean({ defaultValue: true }),
|
||||
headers: schema.nullable(HeadersSchema),
|
||||
hasAuth: AuthConfiguration.hasAuth,
|
||||
authType: AuthConfiguration.authType,
|
||||
certType: AuthConfiguration.certType,
|
||||
ca: AuthConfiguration.ca,
|
||||
verificationMode: AuthConfiguration.verificationMode,
|
||||
};
|
||||
|
||||
export const ExternalIncidentServiceConfigurationSchema = schema.object(
|
||||
ExternalIncidentServiceConfiguration
|
||||
);
|
||||
|
||||
export const ExternalIncidentServiceSecretConfiguration = {
|
||||
user: schema.nullable(schema.string()),
|
||||
password: schema.nullable(schema.string()),
|
||||
};
|
||||
|
||||
export const ExternalIncidentServiceSecretConfigurationSchema = schema.object(
|
||||
ExternalIncidentServiceSecretConfiguration
|
||||
);
|
||||
|
||||
export const ExecutorSubActionPushParamsSchema = schema.object({
|
||||
incident: schema.object({
|
||||
title: schema.string(),
|
||||
|
@ -93,3 +88,5 @@ export const ExecutorParamsSchema = schema.oneOf([
|
|||
subActionParams: ExecutorSubActionPushParamsSchema,
|
||||
}),
|
||||
]);
|
||||
|
||||
export const ExternalIncidentServiceSecretConfigurationSchema = SecretConfigurationSchema;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -12,6 +12,7 @@ import { renderMustacheStringNoEscape } from '@kbn/actions-plugin/server/lib/mus
|
|||
import { request } from '@kbn/actions-plugin/server/lib/axios_utils';
|
||||
import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config';
|
||||
import { combineHeadersWithBasicAuthHeader } from '@kbn/actions-plugin/server/lib';
|
||||
import { buildConnectorAuth, validateConnectorAuthConfiguration } from '../../../common/auth/utils';
|
||||
import { validateAndNormalizeUrl, validateJson } from './validators';
|
||||
import {
|
||||
createServiceError,
|
||||
|
@ -20,12 +21,11 @@ import {
|
|||
removeSlash,
|
||||
throwDescriptiveErrorIfResponseIsNotValid,
|
||||
} from './utils';
|
||||
import {
|
||||
import type {
|
||||
CreateIncidentParams,
|
||||
ExternalServiceCredentials,
|
||||
ExternalService,
|
||||
CasesWebhookPublicConfigurationType,
|
||||
CasesWebhookSecretConfigurationType,
|
||||
ExternalServiceIncidentResponse,
|
||||
GetIncidentResponse,
|
||||
UpdateIncidentParams,
|
||||
|
@ -51,31 +51,41 @@ export const createExternalService = (
|
|||
getIncidentResponseExternalTitleKey,
|
||||
getIncidentUrl,
|
||||
hasAuth,
|
||||
authType,
|
||||
headers,
|
||||
viewIncidentUrl,
|
||||
updateIncidentJson,
|
||||
updateIncidentMethod,
|
||||
updateIncidentUrl,
|
||||
verificationMode,
|
||||
ca,
|
||||
} = config as CasesWebhookPublicConfigurationType;
|
||||
const { password, user } = secrets as CasesWebhookSecretConfigurationType;
|
||||
if (
|
||||
!getIncidentUrl ||
|
||||
!createIncidentUrlConfig ||
|
||||
!viewIncidentUrl ||
|
||||
!updateIncidentUrl ||
|
||||
(hasAuth && (!password || !user))
|
||||
) {
|
||||
|
||||
const { basicAuth, sslOverrides } = buildConnectorAuth({
|
||||
hasAuth,
|
||||
authType,
|
||||
secrets,
|
||||
verificationMode,
|
||||
ca,
|
||||
});
|
||||
|
||||
validateConnectorAuthConfiguration({
|
||||
hasAuth,
|
||||
authType,
|
||||
basicAuth,
|
||||
sslOverrides,
|
||||
connectorName: i18n.NAME,
|
||||
});
|
||||
|
||||
if (!getIncidentUrl || !createIncidentUrlConfig || !viewIncidentUrl || !updateIncidentUrl) {
|
||||
throw Error(`[Action]${i18n.NAME}: Wrong configuration.`);
|
||||
}
|
||||
|
||||
const createIncidentUrl = removeSlash(createIncidentUrlConfig);
|
||||
const headersWithBasicAuth = hasAuth
|
||||
? combineHeadersWithBasicAuthHeader({
|
||||
username: user ?? undefined,
|
||||
password: password ?? undefined,
|
||||
headers,
|
||||
})
|
||||
: {};
|
||||
const headersWithBasicAuth = combineHeadersWithBasicAuthHeader({
|
||||
username: basicAuth.auth?.username,
|
||||
password: basicAuth.auth?.password,
|
||||
headers,
|
||||
});
|
||||
|
||||
const axiosInstance = axios.create({
|
||||
headers: {
|
||||
|
@ -84,6 +94,8 @@ export const createExternalService = (
|
|||
},
|
||||
});
|
||||
|
||||
const createIncidentUrl = removeSlash(createIncidentUrlConfig);
|
||||
|
||||
const getIncident = async (id: string): Promise<GetIncidentResponse> => {
|
||||
try {
|
||||
const getUrl = renderMustacheStringNoEscape(getIncidentUrl, {
|
||||
|
@ -104,6 +116,7 @@ export const createExternalService = (
|
|||
url: normalizedUrl,
|
||||
logger,
|
||||
configurationUtilities,
|
||||
sslOverrides,
|
||||
});
|
||||
|
||||
throwDescriptiveErrorIfResponseIsNotValid({
|
||||
|
@ -148,6 +161,7 @@ export const createExternalService = (
|
|||
method: createIncidentMethod,
|
||||
data: json,
|
||||
configurationUtilities,
|
||||
sslOverrides,
|
||||
});
|
||||
|
||||
const { status, statusText, data } = res;
|
||||
|
@ -231,6 +245,7 @@ export const createExternalService = (
|
|||
logger,
|
||||
data: json,
|
||||
configurationUtilities,
|
||||
sslOverrides,
|
||||
});
|
||||
|
||||
throwDescriptiveErrorIfResponseIsNotValid({
|
||||
|
@ -303,6 +318,7 @@ export const createExternalService = (
|
|||
logger,
|
||||
data: json,
|
||||
configurationUtilities,
|
||||
sslOverrides,
|
||||
});
|
||||
|
||||
throwDescriptiveErrorIfResponseIsNotValid({
|
||||
|
|
|
@ -28,12 +28,9 @@ export const CONFIG_ERR = (err: string) =>
|
|||
},
|
||||
});
|
||||
|
||||
export const INVALID_USER_PW = i18n.translate(
|
||||
'xpack.stackConnectors.casesWebhook.invalidUsernamePassword',
|
||||
{
|
||||
defaultMessage: 'both user and password must be specified',
|
||||
}
|
||||
);
|
||||
export const INVALID_AUTH = i18n.translate('xpack.stackConnectors.casesWebhook.invalidSecrets', {
|
||||
defaultMessage: 'must specify a secrets configuration',
|
||||
});
|
||||
|
||||
export const ALLOWED_HOSTS_ERROR = (message: string) =>
|
||||
i18n.translate('xpack.stackConnectors.casesWebhook.configuration.apiAllowedHostsError', {
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { ValidatorServices } from '@kbn/actions-plugin/server/types';
|
||||
import {
|
||||
ExecutorParamsSchema,
|
||||
ExecutorSubActionPushParamsSchema,
|
||||
|
@ -15,13 +14,6 @@ import {
|
|||
ExternalIncidentServiceSecretConfigurationSchema,
|
||||
} from './schema';
|
||||
|
||||
// config definition
|
||||
export enum CasesWebhookMethods {
|
||||
PATCH = 'patch',
|
||||
POST = 'post',
|
||||
PUT = 'put',
|
||||
}
|
||||
|
||||
// config
|
||||
export type CasesWebhookPublicConfigurationType = TypeOf<
|
||||
typeof ExternalIncidentServiceConfigurationSchema
|
||||
|
@ -38,21 +30,6 @@ export interface ExternalServiceCredentials {
|
|||
secrets: CasesWebhookSecretConfigurationType;
|
||||
}
|
||||
|
||||
export interface ExternalServiceValidation {
|
||||
config: (
|
||||
configObject: CasesWebhookPublicConfigurationType,
|
||||
validatorServices: ValidatorServices
|
||||
) => void;
|
||||
secrets: (
|
||||
secrets: CasesWebhookSecretConfigurationType,
|
||||
validatorServices: ValidatorServices
|
||||
) => void;
|
||||
connector: (
|
||||
configObject: CasesWebhookPublicConfigurationType,
|
||||
secrets: CasesWebhookSecretConfigurationType
|
||||
) => string | null;
|
||||
}
|
||||
|
||||
export interface ExternalServiceIncidentResponse {
|
||||
id: string;
|
||||
title: string;
|
||||
|
|
|
@ -7,14 +7,11 @@
|
|||
|
||||
import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/actions_config';
|
||||
import { ValidatorServices } from '@kbn/actions-plugin/server/types';
|
||||
import { isEmpty } from 'lodash';
|
||||
import * as i18n from './translations';
|
||||
import {
|
||||
CasesWebhookPublicConfigurationType,
|
||||
CasesWebhookSecretConfigurationType,
|
||||
ExternalServiceValidation,
|
||||
} from './types';
|
||||
import { CasesWebhookPublicConfigurationType, CasesWebhookSecretConfigurationType } from './types';
|
||||
|
||||
const validateConfig = (
|
||||
export const validateCasesWebhookConfig = (
|
||||
configObject: CasesWebhookPublicConfigurationType,
|
||||
validatorServices: ValidatorServices
|
||||
) => {
|
||||
|
@ -55,26 +52,8 @@ export const validateConnector = (
|
|||
configObject: CasesWebhookPublicConfigurationType,
|
||||
secrets: CasesWebhookSecretConfigurationType
|
||||
): string | null => {
|
||||
// user and password must be set together (or not at all)
|
||||
if (!configObject.hasAuth) return null;
|
||||
if (secrets.password && secrets.user) return null;
|
||||
return i18n.INVALID_USER_PW;
|
||||
};
|
||||
|
||||
export const validateSecrets = (
|
||||
secrets: CasesWebhookSecretConfigurationType,
|
||||
validatorServices: ValidatorServices
|
||||
) => {
|
||||
// user and password must be set together (or not at all)
|
||||
if (!secrets.password && !secrets.user) return;
|
||||
if (secrets.password && secrets.user) return;
|
||||
throw new Error(i18n.INVALID_USER_PW);
|
||||
};
|
||||
|
||||
export const validate: ExternalServiceValidation = {
|
||||
config: validateConfig,
|
||||
secrets: validateSecrets,
|
||||
connector: validateConnector,
|
||||
if (configObject.hasAuth && isEmpty(secrets)) return i18n.INVALID_AUTH;
|
||||
return null;
|
||||
};
|
||||
|
||||
const validProtocols: string[] = ['http:', 'https:'];
|
||||
|
|
|
@ -58,7 +58,7 @@ export type { SlackApiActionParams as SlackApiActionParams } from '../../common/
|
|||
export { ConnectorTypeId as TeamsConnectorTypeId } from './teams';
|
||||
export type { ActionParamsType as TeamsActionParams } from './teams';
|
||||
export { ConnectorTypeId as WebhookConnectorTypeId } from './webhook';
|
||||
export type { ActionParamsType as WebhookActionParams } from './webhook';
|
||||
export type { ActionParamsType as WebhookActionParams } from './webhook/types';
|
||||
export { ConnectorTypeId as XmattersConnectorTypeId } from './xmatters';
|
||||
export type { ActionParamsType as XmattersActionParams } from './xmatters';
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema, Type } from '@kbn/config-schema';
|
||||
|
||||
// TODO: remove once this is merged: https://github.com/elastic/kibana/pull/41728
|
||||
|
||||
export function nullableType<V>(type: Type<V>) {
|
||||
return schema.oneOf([type, schema.literal(null)], { defaultValue: () => null });
|
||||
}
|
|
@ -12,18 +12,14 @@ import { ActionsConfigurationUtilities } from '@kbn/actions-plugin/server/action
|
|||
import { Logger } from '@kbn/core/server';
|
||||
import { actionsMock } from '@kbn/actions-plugin/server/mocks';
|
||||
import axios from 'axios';
|
||||
import {
|
||||
ConnectorTypeConfigType,
|
||||
ConnectorTypeSecretsType,
|
||||
getConnectorType,
|
||||
WebhookConnectorType,
|
||||
WebhookMethods,
|
||||
} from '.';
|
||||
import { ConnectorTypeConfigType, ConnectorTypeSecretsType, WebhookConnectorType } from './types';
|
||||
|
||||
import { getConnectorType } from '.';
|
||||
|
||||
import * as utils from '@kbn/actions-plugin/server/lib/axios_utils';
|
||||
import { loggerMock } from '@kbn/logging-mocks';
|
||||
import { SSLCertType, WebhookAuthType } from '../../../common/webhook/constants';
|
||||
import { PFX_FILE, CRT_FILE, KEY_FILE } from './mocks';
|
||||
import { AuthType, SSLCertType, WebhookMethods } from '../../../common/auth/constants';
|
||||
import { PFX_FILE, CRT_FILE, KEY_FILE } from '../../../common/auth/mocks';
|
||||
|
||||
jest.mock('axios');
|
||||
jest.mock('@kbn/actions-plugin/server/lib/axios_utils', () => {
|
||||
|
@ -157,7 +153,7 @@ describe('config validation', () => {
|
|||
test('config validation passes when only required fields are provided', () => {
|
||||
const config: Record<string, string | boolean> = {
|
||||
url: 'http://mylisteningserver:9200/endpoint',
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
};
|
||||
expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({
|
||||
|
@ -171,7 +167,7 @@ describe('config validation', () => {
|
|||
const config: Record<string, string | boolean> = {
|
||||
url: 'http://mylisteningserver:9200/endpoint',
|
||||
method,
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
};
|
||||
expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({
|
||||
|
@ -198,7 +194,7 @@ describe('config validation', () => {
|
|||
test('config validation passes when a url is specified', () => {
|
||||
const config: Record<string, string | boolean> = {
|
||||
url: 'http://mylisteningserver:9200/endpoint',
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
};
|
||||
expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({
|
||||
|
@ -226,7 +222,7 @@ describe('config validation', () => {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
};
|
||||
expect(validateConfig(connectorType, config, { configurationUtilities })).toEqual({
|
||||
|
@ -257,7 +253,7 @@ describe('config validation', () => {
|
|||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
};
|
||||
|
||||
|
@ -332,7 +328,7 @@ describe('execute()', () => {
|
|||
headers: {
|
||||
aheader: 'a value',
|
||||
},
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
};
|
||||
await connectorType.executor({
|
||||
|
@ -392,7 +388,7 @@ describe('execute()', () => {
|
|||
headers: {
|
||||
aheader: 'a value',
|
||||
},
|
||||
authType: WebhookAuthType.SSL,
|
||||
authType: AuthType.SSL,
|
||||
certType: SSLCertType.CRT,
|
||||
hasAuth: true,
|
||||
};
|
||||
|
@ -575,7 +571,7 @@ describe('execute()', () => {
|
|||
headers: {
|
||||
aheader: 'a value',
|
||||
},
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
};
|
||||
requestMock.mockReset();
|
||||
|
|
|
@ -6,18 +6,16 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { isString } from 'lodash';
|
||||
import axios, { AxiosError, AxiosResponse } from 'axios';
|
||||
import { schema, TypeOf } from '@kbn/config-schema';
|
||||
import { Logger } from '@kbn/core/server';
|
||||
import { pipe } from 'fp-ts/lib/pipeable';
|
||||
import { map, getOrElse } from 'fp-ts/lib/Option';
|
||||
|
||||
import type {
|
||||
ActionType as ConnectorType,
|
||||
ActionTypeExecutorOptions as ConnectorTypeExecutorOptions,
|
||||
ActionTypeExecutorResult as ConnectorTypeExecutorResult,
|
||||
ValidatorServices,
|
||||
} from '@kbn/actions-plugin/server/types';
|
||||
|
||||
import { request } from '@kbn/actions-plugin/server/lib/axios_utils';
|
||||
import {
|
||||
AlertingConnectorFeatureId,
|
||||
|
@ -26,90 +24,23 @@ import {
|
|||
} from '@kbn/actions-plugin/common/types';
|
||||
import { renderMustacheString } from '@kbn/actions-plugin/server/lib/mustache_renderer';
|
||||
import { combineHeadersWithBasicAuthHeader } from '@kbn/actions-plugin/server/lib';
|
||||
import { SSLCertType, WebhookAuthType } from '../../../common/webhook/constants';
|
||||
import { getRetryAfterIntervalFromHeaders } from '../lib/http_response_retry_header';
|
||||
import { nullableType } from '../lib/nullable';
|
||||
import { isOk, promiseResult, Result } from '../lib/result_type';
|
||||
|
||||
// config definition
|
||||
export enum WebhookMethods {
|
||||
POST = 'post',
|
||||
PUT = 'put',
|
||||
}
|
||||
|
||||
export type WebhookConnectorType = ConnectorType<
|
||||
ConnectorTypeConfigType,
|
||||
ConnectorTypeSecretsType,
|
||||
import type {
|
||||
WebhookConnectorType,
|
||||
ActionParamsType,
|
||||
unknown
|
||||
>;
|
||||
export type WebhookConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions<
|
||||
ConnectorTypeConfigType,
|
||||
WebhookConnectorTypeExecutorOptions,
|
||||
ConnectorTypeSecretsType,
|
||||
ActionParamsType
|
||||
>;
|
||||
} from './types';
|
||||
|
||||
const HeadersSchema = schema.recordOf(schema.string(), schema.string());
|
||||
const configSchemaProps = {
|
||||
url: schema.string(),
|
||||
method: schema.oneOf([schema.literal(WebhookMethods.POST), schema.literal(WebhookMethods.PUT)], {
|
||||
defaultValue: WebhookMethods.POST,
|
||||
}),
|
||||
headers: nullableType(HeadersSchema),
|
||||
hasAuth: schema.boolean({ defaultValue: true }),
|
||||
authType: schema.maybe(
|
||||
schema.oneOf(
|
||||
[
|
||||
schema.literal(WebhookAuthType.Basic),
|
||||
schema.literal(WebhookAuthType.SSL),
|
||||
schema.literal(null),
|
||||
],
|
||||
{
|
||||
defaultValue: WebhookAuthType.Basic,
|
||||
}
|
||||
)
|
||||
),
|
||||
certType: schema.maybe(
|
||||
schema.oneOf([schema.literal(SSLCertType.CRT), schema.literal(SSLCertType.PFX)])
|
||||
),
|
||||
ca: schema.maybe(schema.string()),
|
||||
verificationMode: schema.maybe(
|
||||
schema.oneOf([schema.literal('none'), schema.literal('certificate'), schema.literal('full')])
|
||||
),
|
||||
};
|
||||
const ConfigSchema = schema.object(configSchemaProps);
|
||||
export type ConnectorTypeConfigType = TypeOf<typeof ConfigSchema>;
|
||||
|
||||
// secrets definition
|
||||
export type ConnectorTypeSecretsType = TypeOf<typeof SecretsSchema>;
|
||||
const secretSchemaProps = {
|
||||
user: schema.nullable(schema.string()),
|
||||
password: schema.nullable(schema.string()),
|
||||
crt: schema.nullable(schema.string()),
|
||||
key: schema.nullable(schema.string()),
|
||||
pfx: schema.nullable(schema.string()),
|
||||
};
|
||||
const SecretsSchema = schema.object(secretSchemaProps, {
|
||||
validate: (secrets) => {
|
||||
// user and password must be set together (or not at all)
|
||||
if (!secrets.password && !secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return;
|
||||
if (secrets.password && secrets.user && !secrets.crt && !secrets.key && !secrets.pfx) return;
|
||||
if (secrets.crt && secrets.key && !secrets.user && !secrets.pfx) return;
|
||||
if (!secrets.crt && !secrets.key && !secrets.user && secrets.pfx) return;
|
||||
return i18n.translate('xpack.stackConnectors.webhook.invalidUsernamePassword', {
|
||||
defaultMessage:
|
||||
'must specify one of the following schemas: user and password; crt and key (with optional password); or pfx (with optional password)',
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
// params definition
|
||||
export type ActionParamsType = TypeOf<typeof ParamsSchema>;
|
||||
export const ParamsSchema = schema.object({
|
||||
body: schema.maybe(schema.string()),
|
||||
});
|
||||
import { getRetryAfterIntervalFromHeaders } from '../lib/http_response_retry_header';
|
||||
import { isOk, promiseResult, Result } from '../lib/result_type';
|
||||
import { ConfigSchema, ParamsSchema } from './schema';
|
||||
import { buildConnectorAuth } from '../../../common/auth/utils';
|
||||
import { SecretConfigurationSchema } from '../../../common/auth/schema';
|
||||
|
||||
export const ConnectorTypeId = '.webhook';
|
||||
|
||||
// connector type definition
|
||||
export function getConnectorType(): WebhookConnectorType {
|
||||
return {
|
||||
|
@ -129,7 +60,7 @@ export function getConnectorType(): WebhookConnectorType {
|
|||
customValidator: validateConnectorTypeConfig,
|
||||
},
|
||||
secrets: {
|
||||
schema: SecretsSchema,
|
||||
schema: SecretConfigurationSchema,
|
||||
},
|
||||
params: {
|
||||
schema: ParamsSchema,
|
||||
|
@ -202,39 +133,16 @@ export async function executor(
|
|||
const { body: data } = params;
|
||||
|
||||
const secrets: ConnectorTypeSecretsType = execOptions.secrets;
|
||||
// For backwards compatibility with connectors created before authType was added, interpret a
|
||||
// hasAuth: true and undefined authType as basic auth
|
||||
const basicAuth =
|
||||
hasAuth &&
|
||||
(authType === WebhookAuthType.Basic || !authType) &&
|
||||
isString(secrets.user) &&
|
||||
isString(secrets.password)
|
||||
? { auth: { username: secrets.user, password: secrets.password } }
|
||||
: {};
|
||||
|
||||
const sslCertificate =
|
||||
authType === WebhookAuthType.SSL &&
|
||||
((isString(secrets.crt) && isString(secrets.key)) || isString(secrets.pfx))
|
||||
? isString(secrets.pfx)
|
||||
? {
|
||||
pfx: Buffer.from(secrets.pfx, 'base64'),
|
||||
...(isString(secrets.password) ? { passphrase: secrets.password } : {}),
|
||||
}
|
||||
: {
|
||||
cert: Buffer.from(secrets.crt!, 'base64'),
|
||||
key: Buffer.from(secrets.key!, 'base64'),
|
||||
...(isString(secrets.password) ? { passphrase: secrets.password } : {}),
|
||||
}
|
||||
: {};
|
||||
const { basicAuth, sslOverrides } = buildConnectorAuth({
|
||||
hasAuth,
|
||||
authType,
|
||||
secrets,
|
||||
verificationMode,
|
||||
ca,
|
||||
});
|
||||
|
||||
const axiosInstance = axios.create();
|
||||
|
||||
const sslOverrides = {
|
||||
...sslCertificate,
|
||||
...(verificationMode ? { verificationMode } : {}),
|
||||
...(ca ? { ca: Buffer.from(ca, 'base64') } : {}),
|
||||
};
|
||||
|
||||
const headersWithBasicAuth = combineHeadersWithBasicAuthHeader({
|
||||
username: basicAuth.auth?.username,
|
||||
password: basicAuth.auth?.password,
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { AuthConfiguration } from '../../../common/auth/schema';
|
||||
import { WebhookMethods } from '../../../common/auth/constants';
|
||||
|
||||
export const HeadersSchema = schema.recordOf(schema.string(), schema.string());
|
||||
|
||||
const configSchemaProps = {
|
||||
url: schema.string(),
|
||||
method: schema.oneOf([schema.literal(WebhookMethods.POST), schema.literal(WebhookMethods.PUT)], {
|
||||
defaultValue: WebhookMethods.POST,
|
||||
}),
|
||||
headers: schema.nullable(HeadersSchema),
|
||||
hasAuth: AuthConfiguration.hasAuth,
|
||||
authType: AuthConfiguration.authType,
|
||||
certType: AuthConfiguration.certType,
|
||||
ca: AuthConfiguration.ca,
|
||||
verificationMode: AuthConfiguration.verificationMode,
|
||||
};
|
||||
|
||||
export const ConfigSchema = schema.object(configSchemaProps);
|
||||
|
||||
// params definition
|
||||
export const ParamsSchema = schema.object({
|
||||
body: schema.maybe(schema.string()),
|
||||
});
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import { TypeOf } from '@kbn/config-schema';
|
||||
import type {
|
||||
ActionType as ConnectorType,
|
||||
ActionTypeExecutorOptions as ConnectorTypeExecutorOptions,
|
||||
} from '@kbn/actions-plugin/server/types';
|
||||
import { ParamsSchema, ConfigSchema } from './schema';
|
||||
import { SecretConfigurationSchema } from '../../../common/auth/schema';
|
||||
|
||||
export type WebhookConnectorType = ConnectorType<
|
||||
ConnectorTypeConfigType,
|
||||
ConnectorTypeSecretsType,
|
||||
ActionParamsType,
|
||||
unknown
|
||||
>;
|
||||
export type WebhookConnectorTypeExecutorOptions = ConnectorTypeExecutorOptions<
|
||||
ConnectorTypeConfigType,
|
||||
ConnectorTypeSecretsType,
|
||||
ActionParamsType
|
||||
>;
|
||||
|
||||
export type ConnectorTypeConfigType = TypeOf<typeof ConfigSchema>;
|
||||
|
||||
// secrets definition
|
||||
export type ConnectorTypeSecretsType = TypeOf<typeof SecretConfigurationSchema>;
|
||||
|
||||
// params definition
|
||||
export type ActionParamsType = TypeOf<typeof ParamsSchema>;
|
|
@ -10,7 +10,7 @@ import { SlackApiParamsSchema } from '../common/slack_api/schema';
|
|||
|
||||
export { ParamsSchema as SlackParamsSchema } from './connector_types/slack';
|
||||
export { ParamsSchema as EmailParamsSchema } from './connector_types/email';
|
||||
export { ParamsSchema as WebhookParamsSchema } from './connector_types/webhook';
|
||||
export { ParamsSchema as WebhookParamsSchema } from './connector_types/webhook/schema';
|
||||
export { ExecutorParamsSchema as JiraParamsSchema } from './connector_types/jira/schema';
|
||||
export { ParamsSchema as PagerdutyParamsSchema } from './connector_types/pagerduty';
|
||||
|
||||
|
|
|
@ -39312,7 +39312,6 @@
|
|||
"xpack.stackConnectors.xmatters.invalidUrlError": "secretsUrl non valide : {err}",
|
||||
"xpack.stackConnectors.xmatters.postingRetryErrorMessage": "Erreur lors du déclenchement du flux xMatters : statut http {status}, réessayer plus tard",
|
||||
"xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "Erreur de déclenchement du flux xMatters : statut inattendu {status}",
|
||||
"xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "l'utilisateur et le mot de passe doivent être spécifiés",
|
||||
"xpack.stackConnectors.casesWebhook.title": "Webhook - Gestion des cas",
|
||||
"xpack.stackConnectors.components.bedrock.accessKeySecret": "Clé d'accès",
|
||||
"xpack.stackConnectors.components.bedrock.apiUrlTextFieldLabel": "URL",
|
||||
|
@ -39327,9 +39326,7 @@
|
|||
"xpack.stackConnectors.components.bedrock.selectMessageText": "Envoyer une requête à Amazon Bedrock.",
|
||||
"xpack.stackConnectors.components.bedrock.title": "Amazon Bedrock",
|
||||
"xpack.stackConnectors.components.bedrock.urlTextFieldLabel": "URL",
|
||||
"xpack.stackConnectors.components.casesWebhook.addHeaderButton": "Ajouter",
|
||||
"xpack.stackConnectors.components.casesWebhook.addVariable": "Ajouter une variable",
|
||||
"xpack.stackConnectors.components.casesWebhook.authenticationLabel": "Authentification",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Commentaire de cas Kibana",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Description de cas Kibana",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseIdDesc": "ID de cas Kibana",
|
||||
|
@ -39352,11 +39349,8 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "Clé externe dans la réponse de création d'un cas",
|
||||
"xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "URL de création d'un cas",
|
||||
"xpack.stackConnectors.components.casesWebhook.criticalLabel": "Critique",
|
||||
"xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "Supprimer",
|
||||
"xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "Description",
|
||||
"xpack.stackConnectors.components.casesWebhook.docLink": "Configuration de Webhook - Connecteur de gestion des cas.",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText": "Le mot de passe est requis.",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "L'objet de création de commentaire doit être un JSON valide.",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "La méthode de création de commentaire est requise.",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "L'URL de création de commentaire doit être au format URL.",
|
||||
|
@ -39381,15 +39375,12 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "URL d'obtention de cas",
|
||||
"xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "Demander une authentification pour ce webhook",
|
||||
"xpack.stackConnectors.components.casesWebhook.highLabel": "Élevé",
|
||||
"xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "En-têtes utilisés",
|
||||
"xpack.stackConnectors.components.casesWebhook.idFieldLabel": "ID de cas",
|
||||
"xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "Éditeur de code",
|
||||
"xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON",
|
||||
"xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "Clé",
|
||||
"xpack.stackConnectors.components.casesWebhook.lowLabel": "Bas",
|
||||
"xpack.stackConnectors.components.casesWebhook.mediumLabel": "Moyenne",
|
||||
"xpack.stackConnectors.components.casesWebhook.next": "Suivant",
|
||||
"xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "Mot de passe",
|
||||
"xpack.stackConnectors.components.casesWebhook.previous": "Précédent",
|
||||
"xpack.stackConnectors.components.casesWebhook.selectMessageText": "Envoyer une requête à un service web de gestion de cas.",
|
||||
"xpack.stackConnectors.components.casesWebhook.severityFieldLabel": "Sévérité",
|
||||
|
@ -39414,9 +39405,6 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "Méthode de mise à jour de cas",
|
||||
"xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "URL d'API pour mettre à jour un cas.",
|
||||
"xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "URL de mise à jour du cas",
|
||||
"xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "Nom d'utilisateur",
|
||||
"xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "Valeur",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "Ajouter un en-tête HTTP",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "URL pour afficher un cas dans le système externe. Utilisez le sélecteur de variable pour ajouter à l'URL l'ID ou le titre du système externe.",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "URL de visualisation de cas externe",
|
||||
"xpack.stackConnectors.components.casesWebhookxpack.stackConnectors.components.casesWebhook.connectorTypeTitle": "Webhook - Données de gestion des cas",
|
||||
|
@ -39766,39 +39754,15 @@
|
|||
"xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "Message",
|
||||
"xpack.stackConnectors.components.teams.selectMessageText": "Envoyer un message à un canal Microsoft Teams.",
|
||||
"xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "Créer une URL de webhook Microsoft Teams",
|
||||
"xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "Ajouter un en-tête",
|
||||
"xpack.stackConnectors.components.webhook.authenticationLabel": "Authentification",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel": "Authentification de base",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel": "Aucun",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel": "Authentification SSL",
|
||||
"xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "Éditeur de code",
|
||||
"xpack.stackConnectors.components.webhook.bodyFieldLabel": "Corps",
|
||||
"xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel": "Fichier CRT et KEY",
|
||||
"xpack.stackConnectors.components.webhook.certTypePfxLabel": "Fichier PFX",
|
||||
"xpack.stackConnectors.components.webhook.connectorTypeTitle": "Données de webhook",
|
||||
"xpack.stackConnectors.components.webhook.editCACallout": "Ce webhook comporte déjà un fichier d'autorité de certificat. Charger un nouveau pour le remplacer.",
|
||||
"xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "L'URL n'est pas valide.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "Le nom d'utilisateur est requis.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredMethodText": "La méthode est requise.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "Le corps est requis.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookCAText": "Le fichier CA est requis.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText": "Le fichier CRT est requis.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText": "Le fichier KEY est requis.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText": "Le mot de passe est requis.",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText": "Le fichier PFX est requis.",
|
||||
"xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "Demander une authentification pour ce webhook",
|
||||
"xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "Clé",
|
||||
"xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "Valeur",
|
||||
"xpack.stackConnectors.components.webhook.methodTextFieldLabel": "Méthode",
|
||||
"xpack.stackConnectors.components.webhook.passphraseTextFieldLabel": "Phrase secrète",
|
||||
"xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "Mot de passe",
|
||||
"xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "Clé",
|
||||
"xpack.stackConnectors.components.webhook.selectMessageText": "Envoyer une requête à un service Web.",
|
||||
"xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL",
|
||||
"xpack.stackConnectors.components.webhook.userTextFieldLabel": "Nom d'utilisateur",
|
||||
"xpack.stackConnectors.components.webhook.verificationModeFieldLabel": "Mode de vérification",
|
||||
"xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch": "Ajouter une autorité de certificat",
|
||||
"xpack.stackConnectors.components.webhook.viewHeadersSwitch": "Ajouter un en-tête HTTP",
|
||||
"xpack.stackConnectors.components.xmatters.authenticationLabel": "Authentification",
|
||||
"xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "Authentification de base",
|
||||
"xpack.stackConnectors.components.xmatters.basicAuthLabel": "Authentification de base",
|
||||
|
@ -39975,7 +39939,6 @@
|
|||
"xpack.stackConnectors.webhook.authConfigurationError": "erreur lors de la configuration d'action webhook : authType doit être null ou undefined si hasAuth est faux",
|
||||
"xpack.stackConnectors.webhook.invalidResponseErrorMessage": "erreur lors de l'appel de webhook, réponse non valide",
|
||||
"xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "erreur lors de l'appel de webhook, réessayer ultérieurement",
|
||||
"xpack.stackConnectors.webhook.invalidUsernamePassword": "doit préciser l'un des schémas suivants : utilisateur et mot de passe, crt et key (avec mot de passe facultatif) ou pfx (avec mot de passe facultatif)",
|
||||
"xpack.stackConnectors.webhook.requestFailedErrorMessage": "erreur lors de l'appel de webhook, requête échouée",
|
||||
"xpack.stackConnectors.webhook.title": "Webhook",
|
||||
"xpack.stackConnectors.webhook.unexpectedNullResponseErrorMessage": "réponse nulle inattendue de webhook",
|
||||
|
|
|
@ -39284,7 +39284,6 @@
|
|||
"xpack.stackConnectors.xmatters.invalidUrlError": "無効なsecretsUrl:{err}",
|
||||
"xpack.stackConnectors.xmatters.postingRetryErrorMessage": "xMattersフローのトリガーエラー:HTTPステータス{status}。しばらくたってから再試行してください",
|
||||
"xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "xMattersフローのトリガーエラー:予期しないステータス{status}",
|
||||
"xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "ユーザーとパスワードの両方を指定する必要があります",
|
||||
"xpack.stackConnectors.casesWebhook.title": "Webフック - ケース管理",
|
||||
"xpack.stackConnectors.components.bedrock.accessKeySecret": "アクセスキー",
|
||||
"xpack.stackConnectors.components.bedrock.apiUrlTextFieldLabel": "URL",
|
||||
|
@ -39299,9 +39298,7 @@
|
|||
"xpack.stackConnectors.components.bedrock.selectMessageText": "Amazon Bedrockにリクエストを送信します。",
|
||||
"xpack.stackConnectors.components.bedrock.title": "Amazon Bedrock",
|
||||
"xpack.stackConnectors.components.bedrock.urlTextFieldLabel": "URL",
|
||||
"xpack.stackConnectors.components.casesWebhook.addHeaderButton": "追加",
|
||||
"xpack.stackConnectors.components.casesWebhook.addVariable": "変数を追加",
|
||||
"xpack.stackConnectors.components.casesWebhook.authenticationLabel": "認証",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Kibanaケースコメント",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Kibanaケース説明",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseIdDesc": "KibanaケースID",
|
||||
|
@ -39324,11 +39321,8 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "ケース対応の作成の外部キー",
|
||||
"xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "ケースURLを作成",
|
||||
"xpack.stackConnectors.components.casesWebhook.criticalLabel": "重大",
|
||||
"xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "削除",
|
||||
"xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "説明",
|
||||
"xpack.stackConnectors.components.casesWebhook.docLink": "Webフックの構成 - ケース管理コネクター。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText": "パスワードが必要です。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "ユーザー名が必要です。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "コメントオブジェクトの作成は有効なJSONでなければなりません。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "コメントメソッドを作成は必須です。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "コメントURLの作成はURL形式でなければなりません。",
|
||||
|
@ -39353,15 +39347,12 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "ケースURLを取得",
|
||||
"xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "この Web フックの認証が必要です",
|
||||
"xpack.stackConnectors.components.casesWebhook.highLabel": "高",
|
||||
"xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "使用中のヘッダー",
|
||||
"xpack.stackConnectors.components.casesWebhook.idFieldLabel": "ケースID",
|
||||
"xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "コードエディター",
|
||||
"xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON",
|
||||
"xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "キー",
|
||||
"xpack.stackConnectors.components.casesWebhook.lowLabel": "低",
|
||||
"xpack.stackConnectors.components.casesWebhook.mediumLabel": "中",
|
||||
"xpack.stackConnectors.components.casesWebhook.next": "次へ",
|
||||
"xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "パスワード",
|
||||
"xpack.stackConnectors.components.casesWebhook.previous": "前へ",
|
||||
"xpack.stackConnectors.components.casesWebhook.selectMessageText": "ケース管理Webサービスにリクエストを送信します。",
|
||||
"xpack.stackConnectors.components.casesWebhook.severityFieldLabel": "深刻度",
|
||||
|
@ -39386,9 +39377,6 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "ケースメソッドを更新",
|
||||
"xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "ケースを更新するAPI URL。",
|
||||
"xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "ケースURLを更新",
|
||||
"xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "ユーザー名",
|
||||
"xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "値",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "HTTP ヘッダーを追加",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "外部システムでケースを表示するURL。変数セレクターを使用して、外部システムIDまたは外部システムタイトルをURLに追加します。",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "外部ケース表示URL",
|
||||
"xpack.stackConnectors.components.casesWebhookxpack.stackConnectors.components.casesWebhook.connectorTypeTitle": "Webフック - ケース管理データ",
|
||||
|
@ -39738,39 +39726,15 @@
|
|||
"xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "メッセージ",
|
||||
"xpack.stackConnectors.components.teams.selectMessageText": "メッセージを Microsoft Teams チャネルに送信します。",
|
||||
"xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "Microsoft Teams Web フック URL を作成",
|
||||
"xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "ヘッダーを追加",
|
||||
"xpack.stackConnectors.components.webhook.authenticationLabel": "認証",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel": "基本認証",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel": "なし",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel": "SSL認証",
|
||||
"xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "コードエディター",
|
||||
"xpack.stackConnectors.components.webhook.bodyFieldLabel": "本文",
|
||||
"xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel": "CRTおよびKEYファイル",
|
||||
"xpack.stackConnectors.components.webhook.certTypePfxLabel": "PFXファイル",
|
||||
"xpack.stackConnectors.components.webhook.connectorTypeTitle": "Web フックデータ",
|
||||
"xpack.stackConnectors.components.webhook.editCACallout": "このWebフックには既存の認証局ファイルがあります。新しいファイルをアップロードして置き換えてください。",
|
||||
"xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "URL が無効です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "ユーザー名が必要です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredMethodText": "メソッドが必要です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "本文が必要です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookCAText": "CAファイルが必要です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText": "CRTファイルが必要です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText": "KEYファイルが必要です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText": "パスワードが必要です。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText": "PFXファイルが必要です。",
|
||||
"xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "この Web フックの認証が必要です",
|
||||
"xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "キー",
|
||||
"xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "値",
|
||||
"xpack.stackConnectors.components.webhook.methodTextFieldLabel": "メソド",
|
||||
"xpack.stackConnectors.components.webhook.passphraseTextFieldLabel": "パスフレーズ",
|
||||
"xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "パスワード",
|
||||
"xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "キー",
|
||||
"xpack.stackConnectors.components.webhook.selectMessageText": "Web サービスにリクエストを送信してください。",
|
||||
"xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL",
|
||||
"xpack.stackConnectors.components.webhook.userTextFieldLabel": "ユーザー名",
|
||||
"xpack.stackConnectors.components.webhook.verificationModeFieldLabel": "認証モード",
|
||||
"xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch": "認証局を追加",
|
||||
"xpack.stackConnectors.components.webhook.viewHeadersSwitch": "HTTP ヘッダーを追加",
|
||||
"xpack.stackConnectors.components.xmatters.authenticationLabel": "認証",
|
||||
"xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "基本認証",
|
||||
"xpack.stackConnectors.components.xmatters.basicAuthLabel": "基本認証",
|
||||
|
@ -39947,7 +39911,6 @@
|
|||
"xpack.stackConnectors.webhook.authConfigurationError": "webフックアクションの構成エラー:hasAuthがfalseの場合、authTypeはnullまたはundefinedでなければなりません",
|
||||
"xpack.stackConnectors.webhook.invalidResponseErrorMessage": "Webフックの呼び出しエラー、無効な応答",
|
||||
"xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "Webフックの呼び出しエラー、後ほど再試行",
|
||||
"xpack.stackConnectors.webhook.invalidUsernamePassword": "ユーザーとパスワード、crtと鍵(任意のパスワード付き)、またはpfx(任意のパスワード付き)のいずれかのスキーマを指定する必要があります",
|
||||
"xpack.stackConnectors.webhook.requestFailedErrorMessage": "Webフックの呼び出しエラー。要求が失敗しました",
|
||||
"xpack.stackConnectors.webhook.title": "Web フック",
|
||||
"xpack.stackConnectors.webhook.unexpectedNullResponseErrorMessage": "Webフックからの予期しないnull応答",
|
||||
|
|
|
@ -39330,7 +39330,6 @@
|
|||
"xpack.stackConnectors.xmatters.invalidUrlError": "secretsUrl 无效:{err}",
|
||||
"xpack.stackConnectors.xmatters.postingRetryErrorMessage": "触发 xMatters 流时出错:http 状态为 {status},请稍后重试",
|
||||
"xpack.stackConnectors.xmatters.unexpectedStatusErrorMessage": "触发 xMatters 流时出错:非预期状态 {status}",
|
||||
"xpack.stackConnectors.casesWebhook.invalidUsernamePassword": "必须指定用户及密码",
|
||||
"xpack.stackConnectors.casesWebhook.title": "Webhook - 案例管理",
|
||||
"xpack.stackConnectors.components.bedrock.accessKeySecret": "访问密钥",
|
||||
"xpack.stackConnectors.components.bedrock.apiUrlTextFieldLabel": "URL",
|
||||
|
@ -39345,9 +39344,7 @@
|
|||
"xpack.stackConnectors.components.bedrock.selectMessageText": "向 Amazon Bedrock 发送请求。",
|
||||
"xpack.stackConnectors.components.bedrock.title": "Amazon Bedrock",
|
||||
"xpack.stackConnectors.components.bedrock.urlTextFieldLabel": "URL",
|
||||
"xpack.stackConnectors.components.casesWebhook.addHeaderButton": "添加",
|
||||
"xpack.stackConnectors.components.casesWebhook.addVariable": "添加变量",
|
||||
"xpack.stackConnectors.components.casesWebhook.authenticationLabel": "身份验证",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseCommentDesc": "Kibana 案例注释",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseDescriptionDesc": "Kibana 案例描述",
|
||||
"xpack.stackConnectors.components.casesWebhook.caseIdDesc": "Kibana 案例 ID",
|
||||
|
@ -39370,11 +39367,8 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.createIncidentResponseKeyTextFieldLabel": "创建案例响应外部键",
|
||||
"xpack.stackConnectors.components.casesWebhook.createIncidentUrlTextFieldLabel": "创建案例 URL",
|
||||
"xpack.stackConnectors.components.casesWebhook.criticalLabel": "紧急",
|
||||
"xpack.stackConnectors.components.casesWebhook.deleteHeaderButton": "删除",
|
||||
"xpack.stackConnectors.components.casesWebhook.descriptionTextAreaFieldLabel": "描述",
|
||||
"xpack.stackConnectors.components.casesWebhook.docLink": "正在配置 Webhook - 案例管理连接器。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredAuthPasswordText": "“密码”必填。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredAuthUserNameText": "“用户名”必填。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentIncidentText": "创建注释对象必须为有效 JSON。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentMethodText": "“创建注释方法”必填。",
|
||||
"xpack.stackConnectors.components.casesWebhook.error.requiredCreateCommentUrlText": "创建注释 URL 必须为 URL 格式。",
|
||||
|
@ -39399,15 +39393,12 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.getIncidentUrlTextFieldLabel": "获取案例 URL",
|
||||
"xpack.stackConnectors.components.casesWebhook.hasAuthSwitchLabel": "此 Webhook 需要身份验证",
|
||||
"xpack.stackConnectors.components.casesWebhook.highLabel": "高",
|
||||
"xpack.stackConnectors.components.casesWebhook.httpHeadersTitle": "在用的标头",
|
||||
"xpack.stackConnectors.components.casesWebhook.idFieldLabel": "案例 ID",
|
||||
"xpack.stackConnectors.components.casesWebhook.jsonCodeEditorAriaLabel": "代码编辑器",
|
||||
"xpack.stackConnectors.components.casesWebhook.jsonFieldLabel": "JSON",
|
||||
"xpack.stackConnectors.components.casesWebhook.keyTextFieldLabel": "钥匙",
|
||||
"xpack.stackConnectors.components.casesWebhook.lowLabel": "低",
|
||||
"xpack.stackConnectors.components.casesWebhook.mediumLabel": "中",
|
||||
"xpack.stackConnectors.components.casesWebhook.next": "下一步",
|
||||
"xpack.stackConnectors.components.casesWebhook.passwordTextFieldLabel": "密码",
|
||||
"xpack.stackConnectors.components.casesWebhook.previous": "上一步",
|
||||
"xpack.stackConnectors.components.casesWebhook.selectMessageText": "发送请求到案例管理 Web 服务。",
|
||||
"xpack.stackConnectors.components.casesWebhook.severityFieldLabel": "严重性",
|
||||
|
@ -39432,9 +39423,6 @@
|
|||
"xpack.stackConnectors.components.casesWebhook.updateIncidentMethodTextFieldLabel": "更新案例方法",
|
||||
"xpack.stackConnectors.components.casesWebhook.updateIncidentUrlHelp": "用于更新案例的 API URL。",
|
||||
"xpack.stackConnectors.components.casesWebhook.updateIncidentUrlTextFieldLabel": "更新案例 URL",
|
||||
"xpack.stackConnectors.components.casesWebhook.userTextFieldLabel": "用户名",
|
||||
"xpack.stackConnectors.components.casesWebhook.valueTextFieldLabel": "值",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewHeadersSwitch": "添加 HTTP 标头",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewIncidentUrlHelp": "用于查看外部系统中的案例的 URL。使用变量选择器添加外部系统 ID 或外部系统标题到 URL。",
|
||||
"xpack.stackConnectors.components.casesWebhook.viewIncidentUrlTextFieldLabel": "外部案例查看 URL",
|
||||
"xpack.stackConnectors.components.casesWebhookxpack.stackConnectors.components.casesWebhook.connectorTypeTitle": "Webhook - 案例管理数据",
|
||||
|
@ -39784,39 +39772,15 @@
|
|||
"xpack.stackConnectors.components.teams.messageTextAreaFieldLabel": "消息",
|
||||
"xpack.stackConnectors.components.teams.selectMessageText": "向 Microsoft Teams 频道发送消息。",
|
||||
"xpack.stackConnectors.components.teams.webhookUrlHelpLabel": "创建 Microsoft Teams Webhook URL",
|
||||
"xpack.stackConnectors.components.webhook.addHeaderButtonLabel": "添加标头",
|
||||
"xpack.stackConnectors.components.webhook.authenticationLabel": "身份验证",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodBasicLabel": "基本身份验证",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodNoneLabel": "无",
|
||||
"xpack.stackConnectors.components.webhook.authenticationMethodSSLLabel": "SSL 身份验证",
|
||||
"xpack.stackConnectors.components.webhook.bodyCodeEditorAriaLabel": "代码编辑器",
|
||||
"xpack.stackConnectors.components.webhook.bodyFieldLabel": "正文",
|
||||
"xpack.stackConnectors.components.webhook.certTypeCrtKeyLabel": "CRT 和 KEY 文件",
|
||||
"xpack.stackConnectors.components.webhook.certTypePfxLabel": "PFX 文件",
|
||||
"xpack.stackConnectors.components.webhook.connectorTypeTitle": "Webhook 数据",
|
||||
"xpack.stackConnectors.components.webhook.editCACallout": "此 Webhook 具有现有证书颁发机构文件。上传新文件将其替换。",
|
||||
"xpack.stackConnectors.components.webhook.error.invalidUrlTextField": "URL 无效。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredAuthUserNameText": "“用户名”必填。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredMethodText": "“方法”必填",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookBodyText": "“正文”必填。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookCAText": "CA 文件必填。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookCRTText": "CRT 文件必填。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookKEYText": "KEY 文件必填。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookPasswordText": "“密码”必填。",
|
||||
"xpack.stackConnectors.components.webhook.error.requiredWebhookPFXText": "PFX 文件必填。",
|
||||
"xpack.stackConnectors.components.webhook.hasAuthSwitchLabel": "此 Webhook 需要身份验证",
|
||||
"xpack.stackConnectors.components.webhook.headerKeyTextFieldLabel": "钥匙",
|
||||
"xpack.stackConnectors.components.webhook.headerValueTextFieldLabel": "值",
|
||||
"xpack.stackConnectors.components.webhook.methodTextFieldLabel": "方法",
|
||||
"xpack.stackConnectors.components.webhook.passphraseTextFieldLabel": "密码",
|
||||
"xpack.stackConnectors.components.webhook.passwordTextFieldLabel": "密码",
|
||||
"xpack.stackConnectors.components.webhook.removeHeaderIconLabel": "钥匙",
|
||||
"xpack.stackConnectors.components.webhook.selectMessageText": "将请求发送到 Web 服务。",
|
||||
"xpack.stackConnectors.components.webhook.urlTextFieldLabel": "URL",
|
||||
"xpack.stackConnectors.components.webhook.userTextFieldLabel": "用户名",
|
||||
"xpack.stackConnectors.components.webhook.verificationModeFieldLabel": "验证模式",
|
||||
"xpack.stackConnectors.components.webhook.viewCertificateAuthoritySwitch": "添加证书颁发机构",
|
||||
"xpack.stackConnectors.components.webhook.viewHeadersSwitch": "添加 HTTP 标头",
|
||||
"xpack.stackConnectors.components.xmatters.authenticationLabel": "身份验证",
|
||||
"xpack.stackConnectors.components.xmatters.basicAuthButtonGroupLegend": "基本身份验证",
|
||||
"xpack.stackConnectors.components.xmatters.basicAuthLabel": "基本身份验证",
|
||||
|
@ -39993,7 +39957,6 @@
|
|||
"xpack.stackConnectors.webhook.authConfigurationError": "配置 Webhook 操作时出错:如果 hasAuth 为 false,authType 必须为 null 或未定义",
|
||||
"xpack.stackConnectors.webhook.invalidResponseErrorMessage": "调用 webhook 时出错,响应无效",
|
||||
"xpack.stackConnectors.webhook.invalidResponseRetryLaterErrorMessage": "调用 webhook 时出错,请稍后重试",
|
||||
"xpack.stackConnectors.webhook.invalidUsernamePassword": "必须指定以下方案之一:用户和密码;crt 和密钥(密码可选);或 pfx(密码可选)",
|
||||
"xpack.stackConnectors.webhook.requestFailedErrorMessage": "调用 webhook 时出错,请求失败",
|
||||
"xpack.stackConnectors.webhook.title": "Webhook",
|
||||
"xpack.stackConnectors.webhook.unexpectedNullResponseErrorMessage": "来自 Webhook 的异常空响应",
|
||||
|
|
|
@ -202,7 +202,7 @@ export default function casesWebhookTest({ getService }: FtrProviderContext) {
|
|||
statusCode: 400,
|
||||
error: 'Bad Request',
|
||||
message:
|
||||
'error validating action type connector: both user and password must be specified',
|
||||
'error validating action type connector: must specify a secrets configuration',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
import { getCreateExceptionListDetectionSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_schema.mock';
|
||||
import { EXCEPTION_LIST_ITEM_URL, EXCEPTION_LIST_URL } from '@kbn/securitysolution-list-constants';
|
||||
import { getCreateExceptionListItemMinimalSchemaMock } from '@kbn/lists-plugin/common/schemas/request/create_exception_list_item_schema.mock';
|
||||
import { WebhookAuthType } from '@kbn/stack-connectors-plugin/common/webhook/constants';
|
||||
import { AuthType } from '@kbn/stack-connectors-plugin/common/auth/constants';
|
||||
import { BaseDefaultableFields } from '@kbn/security-solution-plugin/common/api/detection_engine';
|
||||
import {
|
||||
binaryToString,
|
||||
|
@ -190,7 +190,7 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
attributes: {
|
||||
actionTypeId: '.webhook',
|
||||
config: {
|
||||
authType: WebhookAuthType.Basic,
|
||||
authType: AuthType.Basic,
|
||||
hasAuth: true,
|
||||
method: 'post',
|
||||
url: 'http://localhost',
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue