Validate HTTP header names in the "Add Webhook" form.

This commit is contained in:
Pēteris Caune 2018-02-15 13:16:13 +02:00
parent 6643a7771b
commit 55d6471156
5 changed files with 69 additions and 30 deletions

View File

@ -1,5 +1,6 @@
import json
from datetime import timedelta as td from datetime import timedelta as td
import json
import re
from django import forms from django import forms
from django.core.validators import RegexValidator from django.core.validators import RegexValidator
@ -55,28 +56,42 @@ class AddUrlForm(forms.Form):
value = forms.URLField(max_length=1000, validators=[WebhookValidator()]) value = forms.URLField(max_length=1000, validators=[WebhookValidator()])
_valid_header_name = re.compile(r'\A[^:\s][^:\r\n]*\Z').match
class AddWebhookForm(forms.Form): class AddWebhookForm(forms.Form):
error_css_class = "has-error" error_css_class = "has-error"
url_down = forms.URLField(max_length=1000, required=False, url_down = forms.URLField(max_length=1000, required=False,
validators=[WebhookValidator()]) validators=[WebhookValidator()])
url_up = forms.URLField(max_length=1000, required=False, url_up = forms.URLField(max_length=1000, required=False,
validators=[WebhookValidator()]) validators=[WebhookValidator()])
post_data = forms.CharField(max_length=1000, required=False) post_data = forms.CharField(max_length=1000, required=False)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super(AddWebhookForm, self).__init__(*args, **kwargs) super(AddWebhookForm, self).__init__(*args, **kwargs)
self.invalid_header_names = set()
self.headers = {} self.headers = {}
if "header_key[]" in self.data and "header_value[]" in self.data: if "header_key[]" in self.data and "header_value[]" in self.data:
keys = self.data.getlist("header_key[]") keys = self.data.getlist("header_key[]")
values = self.data.getlist("header_value[]") values = self.data.getlist("header_value[]")
for key, value in zip(keys, values): for key, value in zip(keys, values):
if key: if not key:
self.headers[key] = value continue
if not _valid_header_name(key):
self.invalid_header_names.add(key)
self.headers[key] = value
def clean(self):
if self.invalid_header_names:
raise forms.ValidationError("Invalid header names")
return self.cleaned_data
def get_value(self): def get_value(self):
val = dict(self.cleaned_data) val = dict(self.cleaned_data)

View File

@ -72,12 +72,27 @@ class AddWebhookTestCase(BaseTestCase):
self.assertEqual(c.value, '{"headers": {}, "post_data": "hello", "url_down": "http://foo.com", "url_up": ""}') self.assertEqual(c.value, '{"headers": {}, "post_data": "hello", "url_down": "http://foo.com", "url_up": ""}')
def test_it_adds_headers(self): def test_it_adds_headers(self):
form = {"url_down": "http://foo.com", "header_key[]": ["test", "test2"], "header_value[]": ["123", "abc"]} form = {
"url_down": "http://foo.com",
"header_key[]": ["test", "test2"],
"header_value[]": ["123", "abc"]
}
self.client.login(username="alice@example.org", password="password") self.client.login(username="alice@example.org", password="password")
r = self.client.post(self.url, form) r = self.client.post(self.url, form)
self.assertRedirects(r, "/integrations/") self.assertRedirects(r, "/integrations/")
c = Channel.objects.get() c = Channel.objects.get()
self.assertEqual(c.value, '{"headers": {"test": "123", "test2": "abc"}, "post_data": "", "url_down": "http://foo.com", "url_up": ""}') self.assertEqual(c.headers, {"test": "123", "test2": "abc"})
def test_it_rejects_bad_header_names(self):
self.client.login(username="alice@example.org", password="password")
form = {
"url_down": "http://example.org",
"header_key[]": ["ill:egal"],
"header_value[]": ["123"]
}
r = self.client.post(self.url, form)
self.assertContains(r, "Please use valid HTTP header names.")
self.assertEqual(Channel.objects.count(), 0)

View File

@ -0,0 +1,4 @@
.webhook-header .error {
border-color: #a94442;
}

View File

@ -34,6 +34,7 @@
<link rel="stylesheet" href="{% static 'css/channel_checks.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/channel_checks.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/log.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/log.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/add_pushover.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/add_pushover.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/add_webhook.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/settings.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/settings.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/last_ping.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/last_ping.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/profile.css' %}" type="text/css"> <link rel="stylesheet" href="{% static 'css/profile.css' %}" type="text/css">

View File

@ -105,32 +105,36 @@
{% endif %} {% endif %}
</div> </div>
</div> </div>
<div class="form-group {{ form.headers.css_classes }}"> <div class="form-group">
<label class="col-sm-2 control-label">Request Headers</label> <label class="col-sm-2 control-label">Request Headers</label>
<div id="webhook-headers" class="col-xs-12 col-sm-10"> <div class="col-xs-12 col-sm-10">
{% for k, v in form.headers.items %} <div id="webhook-headers">
<div class="form-inline webhook-header"> {% for k, v in form.headers.items %}
<input <div class="form-inline webhook-header">
type="text" <input
class="form-control key" type="text"
name="header_key[]" class="form-control key {% if k in form.invalid_header_names %}error{% endif %}"
placeholder="Content-Type" name="header_key[]"
value="{{ k }}" /> placeholder="Content-Type"
<input value="{{ k }}" />
type="text" <input
class="form-control value" type="text"
name="header_value[]" class="form-control value"
placeholder="application/json" name="header_value[]"
value="{{ v }}" /> placeholder="application/json"
<button class="btn btn-default" type="button"> value="{{ v }}" />
<span class="icon-delete"></span> <button class="btn btn-default" type="button">
</button> <span class="icon-delete"></span>
</button>
</div>
{% endfor %}
</div> </div>
{% endfor %} {% if form.invalid_header_names %}
<div class="text-danger">
Please use valid HTTP header names.
</div>
{% endif %}
</div> </div>
<div class="text-center">
</div>
</div> </div>
<div class="form-group"> <div class="form-group">
<div class="col-sm-offset-2 col-sm-10"> <div class="col-sm-offset-2 col-sm-10">