diff --git a/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js b/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js
index 5475224..91727fd 100644
--- a/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js
+++ b/django_forms_workflows/static/django_forms_workflows/js/workflow-builder.js
@@ -4,7 +4,7 @@
* Drag-and-drop workflow builder for post-submission actions and approvals.
*/
-class WorkflowBuilder {
+export class WorkflowBuilder {
constructor(config) {
this.config = config;
this.nodes = [];
@@ -1784,7 +1784,7 @@ class WorkflowBuilder {
fields.forEach(field => {
const prefillBadge = field.prefill_source ?
- ` ${field.prefill_source}` : '';
+ ` ${this.escapeHtml(field.prefill_source)}` : '';
const requiredBadge = field.required ?
`Required` : '';
@@ -1793,7 +1793,7 @@ class WorkflowBuilder {
${this.escapeHtml(field.label)}
- ${field.name} (${field.type})
+ ${this.escapeHtml(field.name)} (${this.escapeHtml(field.type)})
${requiredBadge}
@@ -2893,10 +2893,10 @@ class WorkflowBuilder {
return `${badgeHtml}${fieldCount} field${fieldCount !== 1 ? 's' : ''} •
Edit Form`;
case 'workflow_settings':
const parts_ws = [];
- if (node.data.name_label) parts_ws.push(node.data.name_label);
+ if (node.data.name_label) parts_ws.push(this.escapeHtml(node.data.name_label));
if (node.data.approval_deadline_days) parts_ws.push(`Deadline: ${node.data.approval_deadline_days}d`);
- if (node.data.notification_cadence && node.data.notification_cadence !== 'immediate') parts_ws.push(`Cadence: ${node.data.notification_cadence}`);
- if (node.data.notification_cadence_form_field && node.data.notification_cadence === 'form_field_date') parts_ws.push(`Date field: ${node.data.notification_cadence_form_field}`);
+ if (node.data.notification_cadence && node.data.notification_cadence !== 'immediate') parts_ws.push(`Cadence: ${this.escapeHtml(node.data.notification_cadence)}`);
+ if (node.data.notification_cadence_form_field && node.data.notification_cadence === 'form_field_date') parts_ws.push(`Date field: ${this.escapeHtml(node.data.notification_cadence_form_field)}`);
if (node.data.notification_rules && node.data.notification_rules.length > 0) parts_ws.push(`Notifications: ${node.data.notification_rules.length}`);
if (node.data.trigger_conditions && node.data.trigger_conditions.conditions && node.data.trigger_conditions.conditions.length > 0) parts_ws.push('Conditional');
return parts_ws.length > 0 ?
@@ -2908,14 +2908,14 @@ class WorkflowBuilder {
if (node.data.allow_send_back) stageParts.push('Send Back target');
if (node.data.allow_reassign) stageParts.push('Reassign');
if (node.data.allow_edit_form_data) stageParts.push('Editable');
- if (node.data.assignee_form_field) stageParts.push(`Dynamic: ${node.data.assignee_form_field}`);
+ if (node.data.assignee_form_field) stageParts.push(`Dynamic: ${this.escapeHtml(node.data.assignee_form_field)}`);
if (node.data.approval_fields && node.data.approval_fields.length > 0) stageParts.push(`Stage fields: ${node.data.approval_fields.length}`);
if (node.data.trigger_conditions && node.data.trigger_conditions.conditions && node.data.trigger_conditions.conditions.length > 0) stageParts.push('Conditional');
if (node.data.approval_groups && node.data.approval_groups.length > 0) {
const gc = node.data.approval_groups.length;
- stageParts.push(`${gc} group${gc > 1 ? 's' : ''} (${node.data.approval_logic || 'all'})`);
+ stageParts.push(`${gc} group${gc > 1 ? 's' : ''} (${this.escapeHtml(node.data.approval_logic || 'all')})`);
}
- const label = node.data.approve_label ? ` • "${node.data.approve_label}"` : '';
+ const label = node.data.approve_label ? ` • "${this.escapeHtml(node.data.approve_label)}"` : '';
return stageParts.length > 0 ?
`
Stage ${node.data.order || '?'}${stageParts.join(' + ')}${label}` :
`
Stage ${node.data.order || '?'}No approvers configured`;
@@ -2934,22 +2934,22 @@ class WorkflowBuilder {
'not_empty': 'is not empty'
};
const op = operatorSymbols[node.data.operator] || node.data.operator;
- return `If ${node.data.field} ${op} ${node.data.value || ''}`;
+ return `If ${this.escapeHtml(node.data.field)} ${this.escapeHtml(op)} ${this.escapeHtml(node.data.value || '')}`;
}
return 'Configure condition';
case 'action':
- return node.data.action_type ? `${node.data.action_type.toUpperCase()}: ${node.data.trigger || ''}` : 'Configure action';
+ return node.data.action_type ? `${this.escapeHtml(node.data.action_type.toUpperCase())}: ${this.escapeHtml(node.data.trigger || '')}` : 'Configure action';
case 'email':
- if (node.data.email_to && node.data.email_to_field) return `Send to: ${node.data.email_to} + field ${node.data.email_to_field}`;
- if (node.data.email_to) return `Send to: ${node.data.email_to}`;
- if (node.data.email_to_field) return `Send to field: ${node.data.email_to_field}`;
+ if (node.data.email_to && node.data.email_to_field) return `Send to: ${this.escapeHtml(node.data.email_to)} + field ${this.escapeHtml(node.data.email_to_field)}`;
+ if (node.data.email_to) return `Send to: ${this.escapeHtml(node.data.email_to)}`;
+ if (node.data.email_to_field) return `Send to field: ${this.escapeHtml(node.data.email_to_field)}`;
return 'Configure email';
case 'join':
return '
Parallel stages merge here';
case 'sub_workflow':
const swParts = [];
- if (node.data.sub_workflow_name) swParts.push(node.data.sub_workflow_name);
- if (node.data.count_field) swParts.push(`Count: ${node.data.count_field}`);
+ if (node.data.sub_workflow_name) swParts.push(this.escapeHtml(node.data.sub_workflow_name));
+ if (node.data.count_field) swParts.push(`Count: ${this.escapeHtml(node.data.count_field)}`);
if (node.data.trigger) swParts.push(node.data.trigger === 'on_approval' ? 'After approval' : 'On submission');
if (node.data.detached) swParts.push('Detached');
if (node.data.reject_parent) swParts.push('Rejects parent');
@@ -3162,6 +3162,3 @@ class WorkflowBuilder {
return `M ${x1} ${y1} C ${cx1} ${y1}, ${cx2} ${y2}, ${x2} ${y2}`;
}
}
-
-// This script is loaded before the inline admin bootstrap code.
-window.WorkflowBuilder = WorkflowBuilder;
diff --git a/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html b/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html
index 08a7705..7c3d90a 100644
--- a/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html
+++ b/django_forms_workflows/templates/admin/django_forms_workflows/workflow_builder.html
@@ -1119,8 +1119,9 @@
Visual Builder Scope
{% block extrahead %}
-
-';
+
+ it('escapes workflow_settings name_label/notification_cadence/notification_cadence_form_field', () => {
+ const instance = createInstance();
+ const node = {
+ type: 'workflow_settings',
+ data: {
+ name_label: payload,
+ notification_cadence: 'form_field_date',
+ notification_cadence_form_field: payload,
+ },
+ };
+
+ const html = instance.getNodeDescription(node);
+
+ expect(html).not.toContain('';
+ const node = {
+ id: 'form_1',
+ data: {
+ is_initial: true,
+ form_name: 'Test Form',
+ field_count: 1,
+ fields: [
+ {
+ label: payload,
+ name: payload,
+ type: payload,
+ prefill_source: payload,
+ },
+ ],
+ has_more_fields: false,
+ },
+ };
+
+ const html = instance.buildFormProperties(node);
+
+ expect(html).not.toContain('