Skip to content

Commit 72bc0ea

Browse files
authored
[Alerting] allow email action to not require auth (#60839)
resolves #57143 Currently, the built-in email action requires user/password properties to be set in it's secrets parameters. This PR changes that requirement, so they are no longer required.
1 parent dc31736 commit 72bc0ea

File tree

6 files changed

+181
-43
lines changed

6 files changed

+181
-43
lines changed

x-pack/plugins/actions/server/builtin_action_types/email.test.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -184,12 +184,14 @@ describe('secrets validation', () => {
184184
expect(validateSecrets(actionType, secrets)).toEqual(secrets);
185185
});
186186

187-
test('secrets validation fails when secrets is not valid', () => {
188-
expect(() => {
189-
validateSecrets(actionType, {});
190-
}).toThrowErrorMatchingInlineSnapshot(
191-
`"error validating action type secrets: [user]: expected value of type [string] but got [undefined]"`
192-
);
187+
test('secrets validation succeeds when secrets props are null/undefined', () => {
188+
const secrets: Record<string, any> = {
189+
user: null,
190+
password: null,
191+
};
192+
expect(validateSecrets(actionType, {})).toEqual(secrets);
193+
expect(validateSecrets(actionType, { user: null })).toEqual(secrets);
194+
expect(validateSecrets(actionType, { password: null })).toEqual(secrets);
193195
});
194196
});
195197

x-pack/plugins/actions/server/builtin_action_types/email.ts

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { schema, TypeOf } from '@kbn/config-schema';
1010
import nodemailerGetService from 'nodemailer/lib/well-known';
1111

1212
import { sendEmail, JSON_TRANSPORT_SERVICE } from './lib/send_email';
13-
import { nullableType } from './lib/nullable';
1413
import { portSchema } from './lib/schemas';
1514
import { Logger } from '../../../../../src/core/server';
1615
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types';
@@ -20,10 +19,10 @@ import { ActionsConfigurationUtilities } from '../actions_config';
2019
export type ActionTypeConfigType = TypeOf<typeof ConfigSchema>;
2120

2221
const ConfigSchemaProps = {
23-
service: nullableType(schema.string()),
24-
host: nullableType(schema.string()),
25-
port: nullableType(portSchema()),
26-
secure: nullableType(schema.boolean()),
22+
service: schema.nullable(schema.string()),
23+
host: schema.nullable(schema.string()),
24+
port: schema.nullable(portSchema()),
25+
secure: schema.nullable(schema.boolean()),
2726
from: schema.string(),
2827
};
2928

@@ -75,8 +74,8 @@ function validateConfig(
7574
export type ActionTypeSecretsType = TypeOf<typeof SecretsSchema>;
7675

7776
const SecretsSchema = schema.object({
78-
user: schema.string(),
79-
password: schema.string(),
77+
user: schema.nullable(schema.string()),
78+
password: schema.nullable(schema.string()),
8079
});
8180

8281
// params definition
@@ -144,10 +143,14 @@ async function executor(
144143
const secrets = execOptions.secrets as ActionTypeSecretsType;
145144
const params = execOptions.params as ActionParamsType;
146145

147-
const transport: any = {
148-
user: secrets.user,
149-
password: secrets.password,
150-
};
146+
const transport: any = {};
147+
148+
if (secrets.user != null) {
149+
transport.user = secrets.user;
150+
}
151+
if (secrets.password != null) {
152+
transport.password = secrets.password;
153+
}
151154

152155
if (config.service !== null) {
153156
transport.service = config.service;

x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.test.tsx

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,33 @@ describe('connector validation', () => {
5858
});
5959
});
6060

61+
test('connector validation succeeds when connector config is valid with empty user/password', () => {
62+
const actionConnector = {
63+
secrets: {
64+
user: null,
65+
password: null,
66+
},
67+
id: 'test',
68+
actionTypeId: '.email',
69+
name: 'email',
70+
config: {
71+
72+
port: 2323,
73+
host: 'localhost',
74+
test: 'test',
75+
},
76+
} as EmailActionConnector;
77+
78+
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
79+
errors: {
80+
from: [],
81+
port: [],
82+
host: [],
83+
user: [],
84+
password: [],
85+
},
86+
});
87+
});
6188
test('connector validation fails when connector config is not valid', () => {
6289
const actionConnector = {
6390
secrets: {
@@ -82,6 +109,60 @@ describe('connector validation', () => {
82109
},
83110
});
84111
});
112+
test('connector validation fails when user specified but not password', () => {
113+
const actionConnector = {
114+
secrets: {
115+
user: 'user',
116+
password: null,
117+
},
118+
id: 'test',
119+
actionTypeId: '.email',
120+
name: 'email',
121+
config: {
122+
123+
port: 2323,
124+
host: 'localhost',
125+
test: 'test',
126+
},
127+
} as EmailActionConnector;
128+
129+
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
130+
errors: {
131+
from: [],
132+
port: [],
133+
host: [],
134+
user: [],
135+
password: ['Password is required when username is used.'],
136+
},
137+
});
138+
});
139+
test('connector validation fails when password specified but not user', () => {
140+
const actionConnector = {
141+
secrets: {
142+
user: null,
143+
password: 'password',
144+
},
145+
id: 'test',
146+
actionTypeId: '.email',
147+
name: 'email',
148+
config: {
149+
150+
port: 2323,
151+
host: 'localhost',
152+
test: 'test',
153+
},
154+
} as EmailActionConnector;
155+
156+
expect(actionTypeModel.validateConnector(actionConnector)).toEqual({
157+
errors: {
158+
from: [],
159+
port: [],
160+
host: [],
161+
user: ['Username is required when password is used.'],
162+
password: [],
163+
},
164+
});
165+
});
85166
});
86167

87168
describe('action params validation', () => {

x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/email.tsx

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -97,22 +97,22 @@ export function getActionType(): ActionTypeModel {
9797
)
9898
);
9999
}
100-
if (!action.secrets.user) {
101-
errors.user.push(
100+
if (action.secrets.user && !action.secrets.password) {
101+
errors.password.push(
102102
i18n.translate(
103-
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredUserText',
103+
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPasswordText',
104104
{
105-
defaultMessage: 'Username is required.',
105+
defaultMessage: 'Password is required when username is used.',
106106
}
107107
)
108108
);
109109
}
110-
if (!action.secrets.password) {
111-
errors.password.push(
110+
if (!action.secrets.user && action.secrets.password) {
111+
errors.user.push(
112112
i18n.translate(
113-
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPasswordText',
113+
'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredUserText',
114114
{
115-
defaultMessage: 'Password is required.',
115+
defaultMessage: 'Username is required when password is used.',
116116
}
117117
)
118118
);
@@ -303,7 +303,7 @@ const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP
303303
id="emailUser"
304304
fullWidth
305305
error={errors.user}
306-
isInvalid={errors.user.length > 0 && user !== undefined}
306+
isInvalid={errors.user.length > 0}
307307
label={i18n.translate(
308308
'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel',
309309
{
@@ -313,17 +313,12 @@ const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP
313313
>
314314
<EuiFieldText
315315
fullWidth
316-
isInvalid={errors.user.length > 0 && user !== undefined}
316+
isInvalid={errors.user.length > 0}
317317
name="user"
318318
value={user || ''}
319319
data-test-subj="emailUserInput"
320320
onChange={e => {
321-
editActionSecrets('user', e.target.value);
322-
}}
323-
onBlur={() => {
324-
if (!user) {
325-
editActionSecrets('user', '');
326-
}
321+
editActionSecrets('user', nullableString(e.target.value));
327322
}}
328323
/>
329324
</EuiFormRow>
@@ -333,7 +328,7 @@ const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP
333328
id="emailPassword"
334329
fullWidth
335330
error={errors.password}
336-
isInvalid={errors.password.length > 0 && password !== undefined}
331+
isInvalid={errors.password.length > 0}
337332
label={i18n.translate(
338333
'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.passwordFieldLabel',
339334
{
@@ -343,17 +338,12 @@ const EmailActionConnectorFields: React.FunctionComponent<ActionConnectorFieldsP
343338
>
344339
<EuiFieldPassword
345340
fullWidth
346-
isInvalid={errors.password.length > 0 && password !== undefined}
341+
isInvalid={errors.password.length > 0}
347342
name="password"
348343
value={password || ''}
349344
data-test-subj="emailPasswordInput"
350345
onChange={e => {
351-
editActionSecrets('password', e.target.value);
352-
}}
353-
onBlur={() => {
354-
if (!password) {
355-
editActionSecrets('password', '');
356-
}
346+
editActionSecrets('password', nullableString(e.target.value));
357347
}}
358348
/>
359349
</EuiFormRow>
@@ -624,3 +614,9 @@ const EmailParamsFields: React.FunctionComponent<ActionParamsProps<EmailActionPa
624614
</Fragment>
625615
);
626616
};
617+
618+
// if the string == null or is empty, return null, else return string
619+
function nullableString(str: string | null | undefined) {
620+
if (str == null || str.trim() === '') return null;
621+
return str;
622+
}

x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/types.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ interface EmailConfig {
7272
}
7373

7474
interface EmailSecrets {
75-
user: string;
76-
password: string;
75+
user: string | null;
76+
password: string | null;
7777
}
7878

7979
export interface EmailActionConnector extends ActionConnector {

x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/email.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,5 +227,61 @@ export default function emailTest({ getService }: FtrProviderContext) {
227227
.expect(200);
228228
expect(typeof createdAction.id).to.be('string');
229229
});
230+
231+
it('should handle an email action with no auth', async () => {
232+
const { body: createdAction } = await supertest
233+
.post('/api/action')
234+
.set('kbn-xsrf', 'foo')
235+
.send({
236+
name: 'An email action with no auth',
237+
actionTypeId: '.email',
238+
config: {
239+
service: '__json',
240+
241+
},
242+
})
243+
.expect(200);
244+
245+
await supertest
246+
.post(`/api/action/${createdAction.id}/_execute`)
247+
.set('kbn-xsrf', 'foo')
248+
.send({
249+
params: {
250+
251+
subject: 'email-subject',
252+
message: 'email-message',
253+
},
254+
})
255+
.expect(200)
256+
.then((resp: any) => {
257+
expect(resp.body.data.message.messageId).to.be.a('string');
258+
expect(resp.body.data.messageId).to.be.a('string');
259+
260+
delete resp.body.data.message.messageId;
261+
delete resp.body.data.messageId;
262+
263+
expect(resp.body.data).to.eql({
264+
envelope: {
265+
266+
267+
},
268+
message: {
269+
from: { address: '[email protected]', name: '' },
270+
to: [
271+
{
272+
address: '[email protected]',
273+
name: '',
274+
},
275+
],
276+
cc: null,
277+
bcc: null,
278+
subject: 'email-subject',
279+
html: '<p>email-message</p>\n',
280+
text: 'email-message',
281+
headers: {},
282+
},
283+
});
284+
});
285+
});
230286
});
231287
}

0 commit comments

Comments
 (0)