support server-side webhook key generation

This commit is contained in:
Jake McDermott
2019-09-09 13:04:00 -04:00
committed by Jeff Bradberry
parent 178a2c7c49
commit 5f7bfaa20a
4 changed files with 102 additions and 46 deletions

View File

@@ -794,9 +794,21 @@ angular.module('FormGenerator', [GeneratorHelpers.name, 'Utilities', listGenerat
} }
if (field.genHash) { if (field.genHash) {
html += "<span class=\"input-group-btn input-group-prepend\"><button type=\"button\" class=\"btn Form-lookupButton\" ng-click=\"genHash('" + fld + "')\" " + const defaultGenHashButtonTemplate = `
"aw-tool-tip=\"Generate " + field.label + "\" data-placement=\"top\" id=\"" + this.form.name + "_" + fld + "_gen_btn\">" + <span class="input-group-btn input-group-prepend">
"<i class=\"fa fa-magic\"></i></button></span>\n</div>\n"; <button
type="button"
class="btn Form-lookupButton"
ng-click="genHash('${fld}')"
aw-tool-tip="Generate ${field.label}"
data-placement="top"
id="${this.form.name}_${fld}_gen_btn"
>
<i class="fa fa-refresh" />
</button>
</span>`;
const genHashButtonTemplate = _.get(field, 'genHashButtonTemplate', defaultGenHashButtonTemplate);
html += `${genHashButtonTemplate}\n</div>\n`;
} }
// Add error messages // Add error messages

View File

@@ -44,6 +44,7 @@
$scope.parseType = 'yaml'; $scope.parseType = 'yaml';
$scope.credentialNotPresent = false; $scope.credentialNotPresent = false;
$scope.canGetAllRelatedResources = true; $scope.canGetAllRelatedResources = true;
$scope.webhook_key_help = i18n._('Webhook services can use this as a shared secret.');
// //
// webhook credential - all handlers, dynamic state, etc. live here // webhook credential - all handlers, dynamic state, etc. live here
@@ -105,6 +106,8 @@
$scope.webhookCredential.modalSelectedName = null; $scope.webhookCredential.modalSelectedName = null;
}; };
$scope.handleWebhookKeyButtonClick = () => {};
$('#content-container').append($compile(` $('#content-container').append($compile(`
<at-dialog <at-dialog
title="webhookCredential.modalTitle" title="webhookCredential.modalTitle"
@@ -159,6 +162,9 @@
default_val: false default_val: false
}); });
CallbackHelpInit({ scope: $scope }); CallbackHelpInit({ scope: $scope });
// set initial vals for webhook checkbox
$scope.enable_webhook = false;
master.enable_webhook = false;
$scope.surveyTooltip = i18n._('Please save before adding a survey to this job template.'); $scope.surveyTooltip = i18n._('Please save before adding a survey to this job template.');
@@ -462,6 +468,7 @@
delete data.credential; delete data.credential;
delete data.vault_credential; delete data.vault_credential;
delete data.webhook_url; delete data.webhook_url;
delete data.webhook_key;
data.webhook_credential = $scope.webhookCredential.id; data.webhook_credential = $scope.webhookCredential.id;
if (!data.webhook_credential) { if (!data.webhook_credential) {
data.webhook_service = null; data.webhook_service = null;

View File

@@ -76,24 +76,28 @@ export default
const virtualEnvs = ConfigData.custom_virtualenvs || []; const virtualEnvs = ConfigData.custom_virtualenvs || [];
$scope.custom_virtualenvs_options = virtualEnvs; $scope.custom_virtualenvs_options = virtualEnvs;
$scope.webhook_url_help = i18n._('Webhook services can launch jobs with this job template by making a POST request to this URL.'); $scope.webhook_url_help = i18n._('Webhook services can launch jobs with this job template by making a POST request to this URL.');
$scope.webhook_key_help = i18n._('Webhook services can use this as a shared secret.');
$scope.currentlySavedWebhookKey = webhookKey;
$scope.webhook_key = webhookKey;
// //
// webhook credential - all handlers, dynamic state, etc. live here // webhook credential - all handlers, dynamic state, etc. live here
// //
$scope.webhook_key = webhookKey;
$scope.webhookCredential = { $scope.webhookCredential = {
id: _.get(jobTemplateData, ['summary_fields', 'webhook_credential', 'id']), id: _.get(jobTemplateData, ['summary_fields', 'webhook_credential', 'id']),
name: _.get(jobTemplateData, ['summary_fields', 'webhook_credential', 'name']), name: _.get(jobTemplateData, ['summary_fields', 'webhook_credential', 'name']),
isModalOpen: false, isModalOpen: false,
isModalReady: false, isModalReady: false,
modalTitle: i18n._('Select Webhook Credential'), modalSelectedId: null,
modalSelectedName: null,
modalBaseParams: { modalBaseParams: {
order_by: 'name', order_by: 'name',
page_size: 5, page_size: 5,
credential_type__namespace: `${jobTemplateData.webhook_service}_token`, credential_type__namespace: `${jobTemplateData.webhook_service}_token`,
}, },
modalSelectedId: null, modalTitle: i18n._('Select Webhook Credential'),
modalSelectedName: null,
}; };
$scope.handleWebhookCredentialLookupClick = () => { $scope.handleWebhookCredentialLookupClick = () => {
@@ -125,7 +129,6 @@ export default
$scope.webhookCredential.isModalReady = false; $scope.webhookCredential.isModalReady = false;
$scope.webhookCredential.modalSelectedId = null; $scope.webhookCredential.modalSelectedId = null;
$scope.webhookCredential.modalSelectedName = null; $scope.webhookCredential.modalSelectedName = null;
}; };
$scope.handleWebhookCredentialSelect = () => { $scope.handleWebhookCredentialSelect = () => {
@@ -137,6 +140,23 @@ export default
$scope.webhookCredential.modalSelectedName = null; $scope.webhookCredential.modalSelectedName = null;
}; };
$scope.handleWebhookKeyButtonClick = () => {
Rest.setUrl(jobTemplateData.related.webhook_key);
Wait('start');
Rest.post({})
.then(({ data }) => {
$scope.currentlySavedWebhookKey = data.webhook_key;
$scope.webhook_key = data.webhook_key;
})
.catch(({ data }) => {
const errorMsg = `Failed to generate new webhook key. POST returned status: ${status}`;
ProcessErrors($scope, data, status, form, { hdr: 'Error!', msg: errorMsg });
})
.finally(() => {
Wait('stop');
});
};
$('#content-container').append($compile(` $('#content-container').append($compile(`
<at-dialog <at-dialog
title="webhookCredential.modalTitle" title="webhookCredential.modalTitle"
@@ -185,6 +205,13 @@ export default
$scope.webhookCredential.id = null; $scope.webhookCredential.id = null;
$scope.webhookCredential.name = null; $scope.webhookCredential.name = null;
} }
if (newServiceValue !== newValue) {
if (newServiceValue === jobTemplateData.webhook_service) {
$scope.webhook_key = $scope.currentlySavedWebhookKey;
} else {
$scope.webhook_key = i18n._('A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE');
}
}
} }
}); });
@@ -430,13 +457,14 @@ export default
default_val: dft default_val: dft
}); });
const defaultWebhookKey = ($scope.webhook_key === "" || $scope.webhook_key === null) ? false : true; // set initial vals for webhook checkbox
hashSetup({ if (jobTemplateData.webhook_service) {
scope: $scope, $scope.enable_webhook = true;
master: master, master.enable_webhook = true;
check_field: 'enable_webhooks', } else {
default_val: defaultWebhookKey $scope.enable_webhook = false;
}); master.enable_webhook = false;
}
ParseTypeChange({ ParseTypeChange({
scope: $scope, scope: $scope,
@@ -703,14 +731,6 @@ export default
}); });
}); });
let webhookKeyPromise = Promise.resolve();
if ($scope.webhook_key !== webhookKey) {
Rest.setUrl(jobTemplateData.related.webhook_key);
webhookKeyPromise = Rest.post({ webhook_key: $scope.webhook_key });
}
var orgDefer = $q.defer(); var orgDefer = $q.defer();
var associationDefer = $q.defer(); var associationDefer = $q.defer();
var associatedLabelsDefer = $q.defer(); var associatedLabelsDefer = $q.defer();
@@ -783,7 +803,6 @@ export default
for (var i = 0; i < toPost.length; i++) { for (var i = 0; i < toPost.length; i++) {
defers.push(Rest.post(toPost[i])); defers.push(Rest.post(toPost[i]));
} }
defers.push(webhookKeyPromise);
$q.all(defers) $q.all(defers)
.then(function() { .then(function() {
Wait('stop'); Wait('stop');
@@ -888,11 +907,18 @@ export default
data.skip_tags = (Array.isArray($scope.skip_tags)) ? _.uniq($scope.skip_tags).join() : ""; data.skip_tags = (Array.isArray($scope.skip_tags)) ? _.uniq($scope.skip_tags).join() : "";
delete data.webhook_url; delete data.webhook_url;
data.webhook_credential = $scope.webhookCredential.id;
if (!data.webhook_credential) {
data.webhook_service = null;
}
delete data.webhook_key; delete data.webhook_key;
delete data.enable_webhook;
data.webhook_credential = $scope.webhookCredential.id;
if (!data.webhook_service) {
data.webhook_credential = null;
}
if (!$scope.enable_webhook) {
data.webhook_service = '';
data.webhook_credential = null;
}
Rest.setUrl(defaultUrl + $state.params.job_template_id); Rest.setUrl(defaultUrl + $state.params.job_template_id);
Rest.patch(data) Rest.patch(data)

View File

@@ -339,10 +339,9 @@ function(NotificationsList, i18n) {
dataContainer: "body", dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)'
}, { }, {
name: 'enable_webhooks', name: 'enable_webhook',
label: i18n._('Enable Webhook'), label: i18n._('Webhooks'),
type: 'checkbox', type: 'checkbox',
ngChange: "toggleCallback('webhook_key')",
column: 2, column: 2,
awPopOver: "<p>" + i18n._("Enabled webhook for this job template.") + "</p>", awPopOver: "<p>" + i18n._("Enabled webhook for this job template.") + "</p>",
dataPlacement: 'right', dataPlacement: 'right',
@@ -407,10 +406,9 @@ function(NotificationsList, i18n) {
type:'select', type:'select',
defaultText: i18n._('Choose a Webhook Service'), defaultText: i18n._('Choose a Webhook Service'),
ngOptions: 'svc.label for svc in webhook_service_options track by svc.value', ngOptions: 'svc.label for svc in webhook_service_options track by svc.value',
ngShow: "enable_webhooks && enable_webhooks !== 'false'", ngShow: "enable_webhook && enable_webhook !== 'false'",
ngDisabled: "!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources", ngDisabled: "!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !canGetAllRelatedResources",
id: 'webhook-service-select', id: 'webhook-service-select',
required: false,
column: 1, column: 1,
awPopOver: "<p>" + i18n._("Select a webhook service.") + "</p>", awPopOver: "<p>" + i18n._("Select a webhook service.") + "</p>",
dataTitle: i18n._('Webhook Service'), dataTitle: i18n._('Webhook Service'),
@@ -420,36 +418,49 @@ function(NotificationsList, i18n) {
webhook_url: { webhook_url: {
label: i18n._('Webhook URL'), label: i18n._('Webhook URL'),
type: 'text', type: 'text',
ngShow: "enable_webhooks && enable_webhooks !== 'false'", ngShow: "job_template_obj && enable_webhook && enable_webhook !== 'false'",
column: 2,
awPopOver: "webhook_url_help", awPopOver: "webhook_url_help",
awPopOverWatch: "webhook_url_help", awPopOverWatch: "webhook_url_help",
dataPlacement: 'top', dataPlacement: 'top',
dataTitle: i18n._('Webhook URL'), dataTitle: i18n._('Webhook URL'),
dataContainer: "body", dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)' readonly: true
}, },
webhook_key: { webhook_key: {
label: i18n._('Webhook Key'), label: i18n._('Webhook Key'),
type: 'text', type: 'text',
ngShow: "enable_webhooks && enable_webhooks !== 'false'", ngShow: "enable_webhook && enable_webhook !== 'false'",
genHash: true, genHash: true,
column: 2, genHashButtonTemplate: `
<span
ng-if="job_template_obj && currentlySavedWebhookKey === webhook_key"
class="input-group-btn input-group-prepend"
>
<button
type="button"
class="btn Form-lookupButton"
ng-click="handleWebhookKeyButtonClick()"
aw-tool-tip="${i18n._('Rotate Webhook Key')}"
data-placement="top"
id="job_template_webhook_key_gen_btn"
>
<i class="fa fa-refresh" />
</button>
</span>
`,
genHashButtonClickHandlerName: "handleWebhookKeyButtonClick",
awPopOver: "webhook_key_help", awPopOver: "webhook_key_help",
awPopOverWatch: "webhook_key_help", awPopOverWatch: "webhook_key_help",
dataPlacement: 'right', dataPlacement: 'right',
dataTitle: i18n._("Webhook Config Key"), dataTitle: i18n._("Webhook Key"),
dataContainer: "body", dataContainer: "body",
ngDisabled: '!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate)', readonly: true,
awRequiredWhen: { required: false,
reqExpression: 'enable_webhooks',
alwaysShowAsterisk: true
}
}, },
webhook_credential: { webhook_credential: {
label: i18n._('Webhook Credential'), label: i18n._('Webhook Credential'),
type: 'custom', type: 'custom',
ngShow: "enable_webhooks && enable_webhooks !== 'false'", ngShow: "enable_webhook && enable_webhook !== 'false'",
control: ` control: `
<webhook-credential-input <webhook-credential-input
is-field-disabled="!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !(webhookCredential.modalBaseParams.credential_type__namespace)" is-field-disabled="!(job_template_obj.summary_fields.user_capabilities.edit || canAddJobTemplate) || !(webhookCredential.modalBaseParams.credential_type__namespace)"
@@ -461,7 +472,7 @@ function(NotificationsList, i18n) {
dataTitle: i18n._('Webhook Credential'), dataTitle: i18n._('Webhook Credential'),
dataPlacement: 'right', dataPlacement: 'right',
dataContainer: "body", dataContainer: "body",
ngDisabled: 'canAddJobTemplate', ngDisabled: '!(webhook_key || webhook_key.value)',
required: false, required: false,
}, },
extra_vars: { extra_vars: {