forked from GithubBackups/healthchecks
Validate channel values. Don't allow ftp addresses, http://localhost addresses, invalid email addresses
This commit is contained in:
parent
fa63c9034e
commit
ac4ba079d3
@ -21,9 +21,13 @@ STATUSES = (
|
|||||||
)
|
)
|
||||||
DEFAULT_TIMEOUT = td(days=1)
|
DEFAULT_TIMEOUT = td(days=1)
|
||||||
DEFAULT_GRACE = td(hours=1)
|
DEFAULT_GRACE = td(hours=1)
|
||||||
CHANNEL_KINDS = (("email", "Email"), ("webhook", "Webhook"),
|
CHANNEL_KINDS = (("email", "Email"),
|
||||||
|
("webhook", "Webhook"),
|
||||||
("hipchat", "HipChat"),
|
("hipchat", "HipChat"),
|
||||||
("slack", "Slack"), ("pd", "PagerDuty"), ("po", "Pushover"),
|
("slack", "Slack"),
|
||||||
|
("pd", "PagerDuty"),
|
||||||
|
("po", "Pushover"),
|
||||||
|
("pushbullet", "Pushbullet"),
|
||||||
("victorops", "VictorOps"))
|
("victorops", "VictorOps"))
|
||||||
|
|
||||||
PO_PRIORITIES = {
|
PO_PRIORITIES = {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from django import forms
|
from django import forms
|
||||||
|
from hc.front.validators import WebhookValidator
|
||||||
from hc.api.models import Channel
|
from hc.api.models import Channel
|
||||||
|
|
||||||
|
|
||||||
@ -7,14 +8,14 @@ class NameTagsForm(forms.Form):
|
|||||||
tags = forms.CharField(max_length=500, required=False)
|
tags = forms.CharField(max_length=500, required=False)
|
||||||
|
|
||||||
def clean_tags(self):
|
def clean_tags(self):
|
||||||
l = []
|
result = []
|
||||||
|
|
||||||
for part in self.cleaned_data["tags"].split(" "):
|
for part in self.cleaned_data["tags"].split(" "):
|
||||||
part = part.strip()
|
part = part.strip()
|
||||||
if part != "":
|
if part != "":
|
||||||
l.append(part)
|
result.append(part)
|
||||||
|
|
||||||
return " ".join(l)
|
return " ".join(result)
|
||||||
|
|
||||||
|
|
||||||
class TimeoutForm(forms.Form):
|
class TimeoutForm(forms.Form):
|
||||||
@ -33,11 +34,29 @@ class AddChannelForm(forms.ModelForm):
|
|||||||
return value.strip()
|
return value.strip()
|
||||||
|
|
||||||
|
|
||||||
|
class AddPdForm(forms.Form):
|
||||||
|
error_css_class = "has-error"
|
||||||
|
value = forms.CharField(max_length=20)
|
||||||
|
|
||||||
|
|
||||||
|
class AddEmailForm(forms.Form):
|
||||||
|
error_css_class = "has-error"
|
||||||
|
value = forms.EmailField(max_length=100)
|
||||||
|
|
||||||
|
|
||||||
|
class AddUrlForm(forms.Form):
|
||||||
|
error_css_class = "has-error"
|
||||||
|
value = forms.URLField(max_length=1000, validators=[WebhookValidator()])
|
||||||
|
|
||||||
|
|
||||||
class AddWebhookForm(forms.Form):
|
class AddWebhookForm(forms.Form):
|
||||||
error_css_class = "has-error"
|
error_css_class = "has-error"
|
||||||
|
|
||||||
value_down = forms.URLField(max_length=1000, required=False)
|
value_down = forms.URLField(max_length=1000, required=False,
|
||||||
value_up = forms.URLField(max_length=1000, required=False)
|
validators=[WebhookValidator()])
|
||||||
|
|
||||||
|
value_up = forms.URLField(max_length=1000, required=False,
|
||||||
|
validators=[WebhookValidator()])
|
||||||
|
|
||||||
def get_value(self):
|
def get_value(self):
|
||||||
return "{value_down}\n{value_up}".format(**self.cleaned_data)
|
return "{value_down}\n{value_up}".format(**self.cleaned_data)
|
||||||
|
@ -1,58 +0,0 @@
|
|||||||
from django.test.utils import override_settings
|
|
||||||
|
|
||||||
from hc.api.models import Channel
|
|
||||||
from hc.test import BaseTestCase
|
|
||||||
|
|
||||||
|
|
||||||
@override_settings(PUSHOVER_API_TOKEN="token", PUSHOVER_SUBSCRIPTION_URL="url")
|
|
||||||
class AddChannelTestCase(BaseTestCase):
|
|
||||||
|
|
||||||
def test_it_adds_email(self):
|
|
||||||
url = "/integrations/add/"
|
|
||||||
form = {"kind": "email", "value": "alice@example.org"}
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
r = self.client.post(url, form)
|
|
||||||
|
|
||||||
self.assertRedirects(r, "/integrations/")
|
|
||||||
assert Channel.objects.count() == 1
|
|
||||||
|
|
||||||
def test_team_access_works(self):
|
|
||||||
url = "/integrations/add/"
|
|
||||||
form = {"kind": "email", "value": "bob@example.org"}
|
|
||||||
|
|
||||||
self.client.login(username="bob@example.org", password="password")
|
|
||||||
self.client.post(url, form)
|
|
||||||
|
|
||||||
ch = Channel.objects.get()
|
|
||||||
# Added by bob, but should belong to alice (bob has team access)
|
|
||||||
self.assertEqual(ch.user, self.alice)
|
|
||||||
|
|
||||||
def test_it_trims_whitespace(self):
|
|
||||||
""" Leading and trailing whitespace should get trimmed. """
|
|
||||||
|
|
||||||
url = "/integrations/add/"
|
|
||||||
form = {"kind": "email", "value": " alice@example.org "}
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
self.client.post(url, form)
|
|
||||||
|
|
||||||
q = Channel.objects.filter(value="alice@example.org")
|
|
||||||
self.assertEqual(q.count(), 1)
|
|
||||||
|
|
||||||
def test_it_rejects_bad_kind(self):
|
|
||||||
url = "/integrations/add/"
|
|
||||||
form = {"kind": "dog", "value": "Lassie"}
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
r = self.client.post(url, form)
|
|
||||||
|
|
||||||
assert r.status_code == 400, r.status_code
|
|
||||||
|
|
||||||
def test_instructions_work(self):
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
|
||||||
kinds = ("email", "webhook", "pd", "pushover", "hipchat", "victorops")
|
|
||||||
for frag in kinds:
|
|
||||||
url = "/integrations/add_%s/" % frag
|
|
||||||
r = self.client.get(url)
|
|
||||||
self.assertContains(r, "Integration Settings", status_code=200)
|
|
49
hc/front/tests/test_add_email.py
Normal file
49
hc/front/tests/test_add_email.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class AddPdTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_email/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "Get an email message")
|
||||||
|
|
||||||
|
def test_it_creates_channel(self):
|
||||||
|
form = {"value": "alice@example.org"}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.kind, "email")
|
||||||
|
self.assertEqual(c.value, "alice@example.org")
|
||||||
|
self.assertFalse(c.email_verified)
|
||||||
|
|
||||||
|
def test_team_access_works(self):
|
||||||
|
form = {"value": "bob@example.org"}
|
||||||
|
|
||||||
|
self.client.login(username="bob@example.org", password="password")
|
||||||
|
self.client.post(self.url, form)
|
||||||
|
|
||||||
|
ch = Channel.objects.get()
|
||||||
|
# Added by bob, but should belong to alice (bob has team access)
|
||||||
|
self.assertEqual(ch.user, self.alice)
|
||||||
|
|
||||||
|
def test_it_rejects_bad_email(self):
|
||||||
|
form = {"value": "not an email address"}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertContains(r, "Enter a valid email address.")
|
||||||
|
|
||||||
|
def test_it_trims_whitespace(self):
|
||||||
|
form = {"value": " alice@example.org "}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
self.client.post(self.url, form)
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.value, "alice@example.org")
|
38
hc/front/tests/test_add_hipchat.py
Normal file
38
hc/front/tests/test_add_hipchat.py
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class AddHipChatTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_hipchat/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "appropriate HipChat room")
|
||||||
|
|
||||||
|
def test_it_works(self):
|
||||||
|
form = {"value": "http://example.org"}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.kind, "hipchat")
|
||||||
|
self.assertEqual(c.value, "http://example.org")
|
||||||
|
|
||||||
|
def test_it_rejects_bad_url(self):
|
||||||
|
form = {"value": "not an URL"}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertContains(r, "Enter a valid URL")
|
||||||
|
|
||||||
|
def test_it_trims_whitespace(self):
|
||||||
|
form = {"value": " http://example.org "}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
self.client.post(self.url, form)
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.value, "http://example.org")
|
31
hc/front/tests/test_add_pd.py
Normal file
31
hc/front/tests/test_add_pd.py
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class AddPdTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_pd/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "incident management system")
|
||||||
|
|
||||||
|
def test_it_works(self):
|
||||||
|
form = {"value": "123456"}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.kind, "pd")
|
||||||
|
self.assertEqual(c.value, "123456")
|
||||||
|
|
||||||
|
def test_it_trims_whitespace(self):
|
||||||
|
form = {"value": " 123456 "}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
self.client.post(self.url, form)
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.value, "123456")
|
@ -5,6 +5,11 @@ from hc.test import BaseTestCase
|
|||||||
|
|
||||||
@override_settings(PUSHOVER_API_TOKEN="token", PUSHOVER_SUBSCRIPTION_URL="url")
|
@override_settings(PUSHOVER_API_TOKEN="token", PUSHOVER_SUBSCRIPTION_URL="url")
|
||||||
class AddPushoverTestCase(BaseTestCase):
|
class AddPushoverTestCase(BaseTestCase):
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get("/integrations/add_pushover/")
|
||||||
|
self.assertContains(r, "Subscribe with Pushover")
|
||||||
|
|
||||||
def test_it_adds_channel(self):
|
def test_it_adds_channel(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
|
||||||
|
@ -8,16 +8,22 @@ from mock import patch
|
|||||||
|
|
||||||
@override_settings(PUSHBULLET_CLIENT_ID="t1", PUSHBULLET_CLIENT_SECRET="s1")
|
@override_settings(PUSHBULLET_CLIENT_ID="t1", PUSHBULLET_CLIENT_SECRET="s1")
|
||||||
class AddPushbulletTestCase(BaseTestCase):
|
class AddPushbulletTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_pushbullet/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "Connect Pushbullet")
|
||||||
|
|
||||||
def test_it_shows_instructions(self):
|
def test_it_shows_instructions(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get("/integrations/add_pushbullet/")
|
r = self.client.get(self.url)
|
||||||
self.assertContains(r, "www.pushbullet.com/authorize", status_code=200)
|
self.assertContains(r, "www.pushbullet.com/authorize", status_code=200)
|
||||||
|
|
||||||
@override_settings(PUSHBULLET_CLIENT_ID=None)
|
@override_settings(PUSHBULLET_CLIENT_ID=None)
|
||||||
def test_it_requires_client_id(self):
|
def test_it_requires_client_id(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get("/integrations/add_pushbullet/")
|
r = self.client.get(self.url)
|
||||||
self.assertEqual(r.status_code, 404)
|
self.assertEqual(r.status_code, 404)
|
||||||
|
|
||||||
@patch("hc.front.views.requests.post")
|
@patch("hc.front.views.requests.post")
|
||||||
@ -27,7 +33,7 @@ class AddPushbulletTestCase(BaseTestCase):
|
|||||||
mock_post.return_value.text = json.dumps(oauth_response)
|
mock_post.return_value.text = json.dumps(oauth_response)
|
||||||
mock_post.return_value.json.return_value = oauth_response
|
mock_post.return_value.json.return_value = oauth_response
|
||||||
|
|
||||||
url = "/integrations/add_pushbullet/?code=12345678"
|
url = self.url + "?code=12345678"
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(url, follow=True)
|
r = self.client.get(url, follow=True)
|
||||||
|
@ -1,70 +1,32 @@
|
|||||||
import json
|
|
||||||
|
|
||||||
from django.test.utils import override_settings
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
from hc.api.models import Channel
|
from hc.api.models import Channel
|
||||||
from hc.test import BaseTestCase
|
from hc.test import BaseTestCase
|
||||||
from mock import patch
|
|
||||||
|
|
||||||
|
|
||||||
class AddSlackTestCase(BaseTestCase):
|
class AddSlackTestCase(BaseTestCase):
|
||||||
|
|
||||||
@override_settings(SLACK_CLIENT_ID=None)
|
@override_settings(SLACK_CLIENT_ID=None)
|
||||||
def test_webhook_instructions_work(self):
|
def test_instructions_work(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get("/integrations/add_slack/")
|
r = self.client.get("/integrations/add_slack/")
|
||||||
self.assertContains(r, "Integration Settings", status_code=200)
|
self.assertContains(r, "Integration Settings", status_code=200)
|
||||||
|
|
||||||
@override_settings(SLACK_CLIENT_ID="foo")
|
@override_settings(SLACK_CLIENT_ID=None)
|
||||||
def test_slack_button(self):
|
def test_it_works(self):
|
||||||
self.client.login(username="alice@example.org", password="password")
|
form = {"value": "http://example.org"}
|
||||||
r = self.client.get("/integrations/add_slack/")
|
|
||||||
self.assertContains(r, "slack.com/oauth/authorize", status_code=200)
|
|
||||||
|
|
||||||
@override_settings(SLACK_CLIENT_ID="foo")
|
|
||||||
def test_landing_page(self):
|
|
||||||
r = self.client.get("/integrations/add_slack/")
|
|
||||||
self.assertContains(r, "Before adding Slack integration",
|
|
||||||
status_code=200)
|
|
||||||
|
|
||||||
@patch("hc.front.views.requests.post")
|
|
||||||
def test_it_handles_oauth_response(self, mock_post):
|
|
||||||
oauth_response = {
|
|
||||||
"ok": True,
|
|
||||||
"team_name": "foo",
|
|
||||||
"incoming_webhook": {
|
|
||||||
"url": "http://example.org",
|
|
||||||
"channel": "bar"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mock_post.return_value.text = json.dumps(oauth_response)
|
|
||||||
mock_post.return_value.json.return_value = oauth_response
|
|
||||||
|
|
||||||
url = "/integrations/add_slack_btn/?code=12345678"
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(url, follow=True)
|
r = self.client.post("/integrations/add_slack/", form)
|
||||||
self.assertRedirects(r, "/integrations/")
|
self.assertRedirects(r, "/integrations/")
|
||||||
self.assertContains(r, "The Slack integration has been added!")
|
|
||||||
|
|
||||||
ch = Channel.objects.get()
|
c = Channel.objects.get()
|
||||||
self.assertEqual(ch.slack_team, "foo")
|
self.assertEqual(c.kind, "slack")
|
||||||
self.assertEqual(ch.slack_channel, "bar")
|
self.assertEqual(c.value, "http://example.org")
|
||||||
self.assertEqual(ch.slack_webhook_url, "http://example.org")
|
|
||||||
|
|
||||||
@patch("hc.front.views.requests.post")
|
@override_settings(SLACK_CLIENT_ID=None)
|
||||||
def test_it_handles_oauth_error(self, mock_post):
|
def test_it_rejects_bad_url(self):
|
||||||
oauth_response = {
|
form = {"value": "not an URL"}
|
||||||
"ok": False,
|
|
||||||
"error": "something went wrong"
|
|
||||||
}
|
|
||||||
|
|
||||||
mock_post.return_value.text = json.dumps(oauth_response)
|
|
||||||
mock_post.return_value.json.return_value = oauth_response
|
|
||||||
|
|
||||||
url = "/integrations/add_slack_btn/?code=12345678"
|
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.get(url, follow=True)
|
r = self.client.post("/integrations/add_slack/", form)
|
||||||
self.assertRedirects(r, "/integrations/")
|
self.assertContains(r, "Enter a valid URL")
|
||||||
self.assertContains(r, "something went wrong")
|
|
||||||
|
64
hc/front/tests/test_add_slack_btn.py
Normal file
64
hc/front/tests/test_add_slack_btn.py
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
from mock import patch
|
||||||
|
|
||||||
|
|
||||||
|
class AddSlackBtnTestCase(BaseTestCase):
|
||||||
|
|
||||||
|
@override_settings(SLACK_CLIENT_ID="foo")
|
||||||
|
def test_instructions_work(self):
|
||||||
|
r = self.client.get("/integrations/add_slack/")
|
||||||
|
self.assertContains(r, "Before adding Slack integration",
|
||||||
|
status_code=200)
|
||||||
|
|
||||||
|
@override_settings(SLACK_CLIENT_ID="foo")
|
||||||
|
def test_slack_button(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get("/integrations/add_slack/")
|
||||||
|
self.assertContains(r, "slack.com/oauth/authorize", status_code=200)
|
||||||
|
|
||||||
|
@patch("hc.front.views.requests.post")
|
||||||
|
def test_it_handles_oauth_response(self, mock_post):
|
||||||
|
oauth_response = {
|
||||||
|
"ok": True,
|
||||||
|
"team_name": "foo",
|
||||||
|
"incoming_webhook": {
|
||||||
|
"url": "http://example.org",
|
||||||
|
"channel": "bar"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_post.return_value.text = json.dumps(oauth_response)
|
||||||
|
mock_post.return_value.json.return_value = oauth_response
|
||||||
|
|
||||||
|
url = "/integrations/add_slack_btn/?code=12345678"
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(url, follow=True)
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
self.assertContains(r, "The Slack integration has been added!")
|
||||||
|
|
||||||
|
ch = Channel.objects.get()
|
||||||
|
self.assertEqual(ch.slack_team, "foo")
|
||||||
|
self.assertEqual(ch.slack_channel, "bar")
|
||||||
|
self.assertEqual(ch.slack_webhook_url, "http://example.org")
|
||||||
|
|
||||||
|
@patch("hc.front.views.requests.post")
|
||||||
|
def test_it_handles_oauth_error(self, mock_post):
|
||||||
|
oauth_response = {
|
||||||
|
"ok": False,
|
||||||
|
"error": "something went wrong"
|
||||||
|
}
|
||||||
|
|
||||||
|
mock_post.return_value.text = json.dumps(oauth_response)
|
||||||
|
mock_post.return_value.json.return_value = oauth_response
|
||||||
|
|
||||||
|
url = "/integrations/add_slack_btn/?code=12345678"
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(url, follow=True)
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
self.assertContains(r, "something went wrong")
|
29
hc/front/tests/test_add_victorops.py
Normal file
29
hc/front/tests/test_add_victorops.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
from hc.api.models import Channel
|
||||||
|
from hc.test import BaseTestCase
|
||||||
|
|
||||||
|
|
||||||
|
class AddVictorOpsTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_victorops/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "incident management system")
|
||||||
|
|
||||||
|
def test_it_works(self):
|
||||||
|
form = {"value": "http://example.org"}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
|
c = Channel.objects.get()
|
||||||
|
self.assertEqual(c.kind, "victorops")
|
||||||
|
self.assertEqual(c.value, "http://example.org")
|
||||||
|
|
||||||
|
def test_it_rejects_bad_url(self):
|
||||||
|
form = {"value": "not an URL"}
|
||||||
|
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertContains(r, "Enter a valid URL")
|
@ -3,12 +3,18 @@ from hc.test import BaseTestCase
|
|||||||
|
|
||||||
|
|
||||||
class AddWebhookTestCase(BaseTestCase):
|
class AddWebhookTestCase(BaseTestCase):
|
||||||
|
url = "/integrations/add_webhook/"
|
||||||
|
|
||||||
|
def test_instructions_work(self):
|
||||||
|
self.client.login(username="alice@example.org", password="password")
|
||||||
|
r = self.client.get(self.url)
|
||||||
|
self.assertContains(r, "Webhooks are a simple way")
|
||||||
|
|
||||||
def test_it_adds_two_webhook_urls_and_redirects(self):
|
def test_it_adds_two_webhook_urls_and_redirects(self):
|
||||||
form = {"value_down": "http://foo.com", "value_up": "https://bar.com"}
|
form = {"value_down": "http://foo.com", "value_up": "https://bar.com"}
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.post("/integrations/add_webhook/", form)
|
r = self.client.post(self.url, form)
|
||||||
self.assertRedirects(r, "/integrations/")
|
self.assertRedirects(r, "/integrations/")
|
||||||
|
|
||||||
c = Channel.objects.get()
|
c = Channel.objects.get()
|
||||||
@ -20,26 +26,37 @@ class AddWebhookTestCase(BaseTestCase):
|
|||||||
# Logging in as bob, not alice. Bob has team access so this
|
# Logging in as bob, not alice. Bob has team access so this
|
||||||
# should work.
|
# should work.
|
||||||
self.client.login(username="bob@example.org", password="password")
|
self.client.login(username="bob@example.org", password="password")
|
||||||
self.client.post("/integrations/add_webhook/", form)
|
self.client.post(self.url, form)
|
||||||
|
|
||||||
c = Channel.objects.get()
|
c = Channel.objects.get()
|
||||||
self.assertEqual(c.user, self.alice)
|
self.assertEqual(c.user, self.alice)
|
||||||
self.assertEqual(c.value, "http://foo.com\nhttps://bar.com")
|
self.assertEqual(c.value, "http://foo.com\nhttps://bar.com")
|
||||||
|
|
||||||
def test_it_rejects_non_http_webhook_urls(self):
|
def test_it_rejects_bad_urls(self):
|
||||||
form = {"value_down": "foo", "value_up": "bar"}
|
urls = [
|
||||||
|
# clearly not an URL
|
||||||
|
"foo",
|
||||||
|
# FTP addresses not allowed
|
||||||
|
"ftp://example.org",
|
||||||
|
# no loopback
|
||||||
|
"http://localhost:1234/endpoint",
|
||||||
|
"http://127.0.0.1/endpoint"
|
||||||
|
]
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
r = self.client.post("/integrations/add_webhook/", form)
|
for url in urls:
|
||||||
self.assertContains(r, "Enter a valid URL.")
|
form = {"value_down": url, "value_up": ""}
|
||||||
|
|
||||||
self.assertEqual(Channel.objects.count(), 0)
|
r = self.client.post(self.url, form)
|
||||||
|
self.assertContains(r, "Enter a valid URL.", msg_prefix=url)
|
||||||
|
|
||||||
|
self.assertEqual(Channel.objects.count(), 0)
|
||||||
|
|
||||||
def test_it_handles_empty_down_url(self):
|
def test_it_handles_empty_down_url(self):
|
||||||
form = {"value_down": "", "value_up": "http://foo.com"}
|
form = {"value_down": "", "value_up": "http://foo.com"}
|
||||||
|
|
||||||
self.client.login(username="alice@example.org", password="password")
|
self.client.login(username="alice@example.org", password="password")
|
||||||
self.client.post("/integrations/add_webhook/", form)
|
self.client.post(self.url, form)
|
||||||
|
|
||||||
c = Channel.objects.get()
|
c = Channel.objects.get()
|
||||||
self.assertEqual(c.value, "\nhttp://foo.com")
|
self.assertEqual(c.value, "\nhttp://foo.com")
|
||||||
|
@ -12,7 +12,6 @@ check_urls = [
|
|||||||
|
|
||||||
channel_urls = [
|
channel_urls = [
|
||||||
url(r'^$', views.channels, name="hc-channels"),
|
url(r'^$', views.channels, name="hc-channels"),
|
||||||
url(r'^add/$', views.add_channel, name="hc-add-channel"),
|
|
||||||
url(r'^add_email/$', views.add_email, name="hc-add-email"),
|
url(r'^add_email/$', views.add_email, name="hc-add-email"),
|
||||||
url(r'^add_webhook/$', views.add_webhook, name="hc-add-webhook"),
|
url(r'^add_webhook/$', views.add_webhook, name="hc-add-webhook"),
|
||||||
url(r'^add_pd/$', views.add_pd, name="hc-add-pd"),
|
url(r'^add_pd/$', views.add_pd, name="hc-add-pd"),
|
||||||
|
14
hc/front/validators.py
Normal file
14
hc/front/validators.py
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
from django.core.exceptions import ValidationError
|
||||||
|
from six.moves.urllib_parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
|
class WebhookValidator(object):
|
||||||
|
message = "Enter a valid URL."
|
||||||
|
|
||||||
|
def __call__(self, value):
|
||||||
|
parsed = urlparse(value)
|
||||||
|
if parsed.scheme not in ("http", "https"):
|
||||||
|
raise ValidationError(message=self.message)
|
||||||
|
|
||||||
|
if parsed.hostname in ("127.0.0.1", "localhost"):
|
||||||
|
raise ValidationError(message=self.message)
|
@ -16,7 +16,7 @@ from django.utils.six.moves.urllib.parse import urlencode
|
|||||||
from hc.api.decorators import uuid_or_400
|
from hc.api.decorators import uuid_or_400
|
||||||
from hc.api.models import DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check, Ping
|
from hc.api.models import DEFAULT_GRACE, DEFAULT_TIMEOUT, Channel, Check, Ping
|
||||||
from hc.front.forms import (AddChannelForm, AddWebhookForm, NameTagsForm,
|
from hc.front.forms import (AddChannelForm, AddWebhookForm, NameTagsForm,
|
||||||
TimeoutForm)
|
TimeoutForm, AddUrlForm, AddPdForm, AddEmailForm)
|
||||||
|
|
||||||
|
|
||||||
# from itertools recipes:
|
# from itertools recipes:
|
||||||
@ -291,29 +291,6 @@ def channels(request):
|
|||||||
return render(request, "front/channels.html", ctx)
|
return render(request, "front/channels.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
def do_add_channel(request, data):
|
|
||||||
form = AddChannelForm(data)
|
|
||||||
if form.is_valid():
|
|
||||||
channel = form.save(commit=False)
|
|
||||||
channel.user = request.team.user
|
|
||||||
channel.save()
|
|
||||||
|
|
||||||
channel.assign_all_checks()
|
|
||||||
|
|
||||||
if channel.kind == "email":
|
|
||||||
channel.send_verify_link()
|
|
||||||
|
|
||||||
return redirect("hc-channels")
|
|
||||||
else:
|
|
||||||
return HttpResponseBadRequest()
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
def add_channel(request):
|
|
||||||
assert request.method == "POST"
|
|
||||||
return do_add_channel(request, request.POST)
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
@uuid_or_400
|
@uuid_or_400
|
||||||
def channel_checks(request, code):
|
def channel_checks(request, code):
|
||||||
@ -361,7 +338,20 @@ def remove_channel(request, code):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_email(request):
|
def add_email(request):
|
||||||
ctx = {"page": "channels"}
|
if request.method == "POST":
|
||||||
|
form = AddEmailForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
channel = Channel(user=request.team.user, kind="email")
|
||||||
|
channel.value = form.cleaned_data["value"]
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
channel.assign_all_checks()
|
||||||
|
channel.send_verify_link()
|
||||||
|
return redirect("hc-channels")
|
||||||
|
else:
|
||||||
|
form = AddEmailForm()
|
||||||
|
|
||||||
|
ctx = {"page": "channels", "form": form}
|
||||||
return render(request, "integrations/add_email.html", ctx)
|
return render(request, "integrations/add_email.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@ -385,7 +375,19 @@ def add_webhook(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_pd(request):
|
def add_pd(request):
|
||||||
ctx = {"page": "channels"}
|
if request.method == "POST":
|
||||||
|
form = AddPdForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
channel = Channel(user=request.team.user, kind="pd")
|
||||||
|
channel.value = form.cleaned_data["value"]
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
channel.assign_all_checks()
|
||||||
|
return redirect("hc-channels")
|
||||||
|
else:
|
||||||
|
form = AddPdForm()
|
||||||
|
|
||||||
|
ctx = {"page": "channels", "form": form}
|
||||||
return render(request, "integrations/add_pd.html", ctx)
|
return render(request, "integrations/add_pd.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@ -393,10 +395,24 @@ def add_slack(request):
|
|||||||
if not settings.SLACK_CLIENT_ID and not request.user.is_authenticated:
|
if not settings.SLACK_CLIENT_ID and not request.user.is_authenticated:
|
||||||
return redirect("hc-login")
|
return redirect("hc-login")
|
||||||
|
|
||||||
|
if request.method == "POST":
|
||||||
|
form = AddUrlForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
channel = Channel(user=request.team.user, kind="slack")
|
||||||
|
channel.value = form.cleaned_data["value"]
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
channel.assign_all_checks()
|
||||||
|
return redirect("hc-channels")
|
||||||
|
else:
|
||||||
|
form = AddUrlForm()
|
||||||
|
|
||||||
ctx = {
|
ctx = {
|
||||||
"page": "channels",
|
"page": "channels",
|
||||||
|
"form": form,
|
||||||
"slack_client_id": settings.SLACK_CLIENT_ID
|
"slack_client_id": settings.SLACK_CLIENT_ID
|
||||||
}
|
}
|
||||||
|
|
||||||
return render(request, "integrations/add_slack.html", ctx)
|
return render(request, "integrations/add_slack.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@ -430,7 +446,19 @@ def add_slack_btn(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_hipchat(request):
|
def add_hipchat(request):
|
||||||
ctx = {"page": "channels"}
|
if request.method == "POST":
|
||||||
|
form = AddUrlForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
channel = Channel(user=request.team.user, kind="hipchat")
|
||||||
|
channel.value = form.cleaned_data["value"]
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
channel.assign_all_checks()
|
||||||
|
return redirect("hc-channels")
|
||||||
|
else:
|
||||||
|
form = AddUrlForm()
|
||||||
|
|
||||||
|
ctx = {"page": "channels", "form": form}
|
||||||
return render(request, "integrations/add_hipchat.html", ctx)
|
return render(request, "integrations/add_hipchat.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
@ -526,10 +554,11 @@ def add_pushover(request):
|
|||||||
user_key = request.GET["pushover_user_key"]
|
user_key = request.GET["pushover_user_key"]
|
||||||
priority = int(request.GET["prio"])
|
priority = int(request.GET["prio"])
|
||||||
|
|
||||||
return do_add_channel(request, {
|
channel = Channel(user=request.team.user, kind="po")
|
||||||
"kind": "po",
|
channel.value = "%s|%d" % (user_key, priority)
|
||||||
"value": "%s|%d" % (user_key, priority),
|
channel.save()
|
||||||
})
|
channel.assign_all_checks()
|
||||||
|
return redirect("hc-channels")
|
||||||
|
|
||||||
# Show Integration Settings form
|
# Show Integration Settings form
|
||||||
ctx = {
|
ctx = {
|
||||||
@ -542,7 +571,19 @@ def add_pushover(request):
|
|||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def add_victorops(request):
|
def add_victorops(request):
|
||||||
ctx = {"page": "channels"}
|
if request.method == "POST":
|
||||||
|
form = AddUrlForm(request.POST)
|
||||||
|
if form.is_valid():
|
||||||
|
channel = Channel(user=request.team.user, kind="victorops")
|
||||||
|
channel.value = form.cleaned_data["value"]
|
||||||
|
channel.save()
|
||||||
|
|
||||||
|
channel.assign_all_checks()
|
||||||
|
return redirect("hc-channels")
|
||||||
|
else:
|
||||||
|
form = AddUrlForm()
|
||||||
|
|
||||||
|
ctx = {"page": "channels", "form": form}
|
||||||
return render(request, "integrations/add_victorops.html", ctx)
|
return render(request, "integrations/add_victorops.html", ctx)
|
||||||
|
|
||||||
|
|
||||||
|
@ -24,10 +24,9 @@
|
|||||||
|
|
||||||
<h2>Integration Settings</h2>
|
<h2>Integration Settings</h2>
|
||||||
|
|
||||||
<form method="post" class="form-horizontal" action="{% url 'hc-add-channel' %}">
|
<form method="post" class="form-horizontal" action="{% url 'hc-add-email' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="kind" value="email" />
|
<div class="form-group {{ form.value.css_classes }}">
|
||||||
<div class="form-group">
|
|
||||||
<label for="id_email" class="col-sm-2 control-label">Email</label>
|
<label for="id_email" class="col-sm-2 control-label">Email</label>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<input
|
<input
|
||||||
@ -35,7 +34,14 @@
|
|||||||
type="email"
|
type="email"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="value"
|
name="value"
|
||||||
placeholder="you@example.org">
|
placeholder="you@example.org"
|
||||||
|
value="{{ form.value.value|default:"" }}">
|
||||||
|
|
||||||
|
{% if form.value.errors %}
|
||||||
|
<div class="help-block">
|
||||||
|
{{ form.value.errors|join:"" }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -64,10 +64,9 @@
|
|||||||
|
|
||||||
<h2>Integration Settings</h2>
|
<h2>Integration Settings</h2>
|
||||||
|
|
||||||
<form method="post" class="form-horizontal" action="{% url 'hc-add-channel' %}">
|
<form method="post" class="form-horizontal" action="{% url 'hc-add-hipchat' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="kind" value="hipchat" />
|
<div class="form-group {{ form.value.css_classes }}">
|
||||||
<div class="form-group">
|
|
||||||
<label for="callback-url" class="col-sm-2 control-label">Callback URL</label>
|
<label for="callback-url" class="col-sm-2 control-label">Callback URL</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input
|
<input
|
||||||
@ -75,7 +74,14 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="value"
|
name="value"
|
||||||
placeholder="">
|
placeholder="https://"
|
||||||
|
value="{{ form.value.value|default:"" }}">
|
||||||
|
|
||||||
|
{% if form.value.errors %}
|
||||||
|
<div class="help-block">
|
||||||
|
{{ form.value.errors|join:"" }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -63,10 +63,9 @@
|
|||||||
|
|
||||||
<h2>Integration Settings</h2>
|
<h2>Integration Settings</h2>
|
||||||
|
|
||||||
<form method="post" class="form-horizontal" action="{% url 'hc-add-channel' %}">
|
<form method="post" class="form-horizontal" action="{% url 'hc-add-pd' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="kind" value="pd" />
|
<div class="form-group {{ form.value.css_classes }}">
|
||||||
<div class="form-group">
|
|
||||||
<label for="api-key" class="col-sm-2 control-label">API Key</label>
|
<label for="api-key" class="col-sm-2 control-label">API Key</label>
|
||||||
<div class="col-sm-3">
|
<div class="col-sm-3">
|
||||||
<input
|
<input
|
||||||
@ -74,7 +73,14 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="value"
|
name="value"
|
||||||
placeholder="">
|
placeholder=""
|
||||||
|
value="{{ form.value.value|default:"" }}">
|
||||||
|
|
||||||
|
{% if form.value.errors %}
|
||||||
|
<div class="help-block">
|
||||||
|
{{ form.value.errors|join:"" }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -156,13 +156,26 @@
|
|||||||
|
|
||||||
<h2>Integration Settings</h2>
|
<h2>Integration Settings</h2>
|
||||||
|
|
||||||
<form method="post" class="form-horizontal" action="{% url 'hc-add-channel' %}">
|
<form method="post" class="form-horizontal" action="{% url 'hc-add-slack' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="kind" value="slack" />
|
<div class="form-group {{ form.value.css_classes }}">
|
||||||
<div class="form-group">
|
<label for="callback-url" class="col-sm-2 control-label">
|
||||||
<label class="col-sm-2 control-label">Callback URL</label>
|
Callback URL
|
||||||
|
</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input type="text" class="form-control" name="value" placeholder="">
|
<input
|
||||||
|
id="callback-url"
|
||||||
|
type="text"
|
||||||
|
class="form-control"
|
||||||
|
name="value"
|
||||||
|
placeholder="https://"
|
||||||
|
value="{{ form.value.value|default:"" }}">
|
||||||
|
|
||||||
|
{% if form.value.errors %}
|
||||||
|
<div class="help-block">
|
||||||
|
{{ form.value.errors|join:"" }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
@ -77,10 +77,9 @@
|
|||||||
|
|
||||||
<h2>Integration Settings</h2>
|
<h2>Integration Settings</h2>
|
||||||
|
|
||||||
<form method="post" class="form-horizontal" action="{% url 'hc-add-channel' %}">
|
<form method="post" class="form-horizontal" action="{% url 'hc-add-victorops' %}">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
<input type="hidden" name="kind" value="victorops" />
|
<div class="form-group {{ form.value.css_classes }}">
|
||||||
<div class="form-group">
|
|
||||||
<label for="post-url" class="col-sm-2 control-label">Post URL</label>
|
<label for="post-url" class="col-sm-2 control-label">Post URL</label>
|
||||||
<div class="col-sm-10">
|
<div class="col-sm-10">
|
||||||
<input
|
<input
|
||||||
@ -88,7 +87,14 @@
|
|||||||
type="text"
|
type="text"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
name="value"
|
name="value"
|
||||||
placeholder="">
|
placeholder="https://"
|
||||||
|
value="{{ form.value.value|default:"" }}">
|
||||||
|
|
||||||
|
{% if form.value.errors %}
|
||||||
|
<div class="help-block">
|
||||||
|
{{ form.value.errors|join:"" }}
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user