Repository: readthedocs/readthedocs.org
Issue: Validate url from Webhook notification

We are allowing `''` as URL for webhook notifications.

We need to add better validation to this form.

### Steps to reproduce it

1. Go to Admin -> Notifications
1. Click the Add button under the Webhook Notifications

Sentry: https://sentry.io/read-the-docs/readthedocs-org/issues/789512412/?referrer=github_plugin

Changed files:
readthedocs/projects/forms.py, readthedocs/projects/views/private.py, readthedocs/rtd_tests/tests/test_project_forms.py

diff --git a/readthedocs/projects/forms.py b/readthedocs/projects/forms.py
index 70fcc88d7..467c41ddb 100644
--- a/readthedocs/projects/forms.py
+++ b/readthedocs/projects/forms.py
@@ -564,6 +564,14 @@ class WebHookForm(forms.ModelForm):
         self.project.webhook_notifications.add(self.webhook)
         return self.project
 
+    def clean_url(self):
+        url = self.cleaned_data.get('url')
+        if not url:
+            raise forms.ValidationError(
+                _('This field is required.')
+            )
+        return url
+
     class Meta:
         model = WebHook
         fields = ['url']
diff --git a/readthedocs/projects/views/private.py b/readthedocs/projects/views/private.py
index 46a317613..034281f87 100644
--- a/readthedocs/projects/views/private.py
+++ b/readthedocs/projects/views/private.py
@@ -537,19 +537,18 @@ def project_notifications(request, project_slug):
         slug=project_slug,
     )
 
-    email_form = EmailHookForm(data=request.POST or None, project=project)
-    webhook_form = WebHookForm(data=request.POST or None, project=project)
+    email_form = EmailHookForm(data=None, project=project)
+    webhook_form = WebHookForm(data=None, project=project)
 
     if request.method == 'POST':
-        if email_form.is_valid():
-            email_form.save()
-        if webhook_form.is_valid():
-            webhook_form.save()
-        project_dashboard = reverse(
-            'projects_notifications',
-            args=[project.slug],
-        )
-        return HttpResponseRedirect(project_dashboard)
+        if 'email' in request.POST.keys():
+            email_form = EmailHookForm(data=request.POST, project=project)
+            if email_form.is_valid():
+                email_form.save()
+        elif 'url' in request.POST.keys():
+            webhook_form = WebHookForm(data=request.POST, project=project)
+            if webhook_form.is_valid():
+                webhook_form.save()
 
     emails = project.emailhook_notifications.all()
     urls = project.webhook_notifications.all()
diff --git a/readthedocs/rtd_tests/tests/test_project_forms.py b/readthedocs/rtd_tests/tests/test_project_forms.py
index d5604cb97..475d6cfe7 100644
--- a/readthedocs/rtd_tests/tests/test_project_forms.py
+++ b/readthedocs/rtd_tests/tests/test_project_forms.py
@@ -13,6 +13,7 @@ from django.test import TestCase
 from django.test.utils import override_settings
 from django_dynamic_fixture import get
 from textclassifier.validators import ClassifierValidator
+from django.core.exceptions import ValidationError
 
 from readthedocs.builds.constants import LATEST
 from readthedocs.builds.models import Version
@@ -31,6 +32,8 @@ from readthedocs.projects.forms import (
     ProjectExtraForm,
     TranslationForm,
     UpdateProjectForm,
+    WebHookForm,
+    EmailHookForm
 )
 from readthedocs.projects.models import Project, EnvironmentVariable
 
@@ -514,6 +517,72 @@ class TestTranslationForms(TestCase):
         self.assertTrue(form.is_valid())
 
 
+class TestNotificationForm(TestCase):
+
+    def setUp(self):
+        self.project = get(Project)
+        
+    def test_webhookform(self):
+        self.assertEqual(self.project.webhook_notifications.all().count(), 0)
+
+        data = {
+            'url': 'http://www.example.com/'
+        }
+        form = WebHookForm(data=data, project=self.project)
+        self.assertTrue(form.is_valid())
+        form.save()
+        self.assertEqual(self.project.webhook_notifications.all().count(), 1)
+
+    def test_wrong_inputs_in_webhookform(self):
+        self.assertEqual(self.project.webhook_notifications.all().count(), 0)
+
+        data = {
+            'url': ''
+        }
+        form = WebHookForm(data=data, project=self.project)
+        self.assertFalse(form.is_valid())
+        self.assertDictEqual(form.errors, {'url': ['This field is required.']})
+        self.assertEqual(self.project.webhook_notifications.all().count(), 0)
+
+        data = {
+            'url': 'wrong-url'
+        }
+        form = WebHookForm(data=data, project=self.project)
+        self.assertFalse(form.is_valid())
+        self.assertDictEqual(form.errors, {'url': ['Enter a valid URL.']})
+        self.assertEqual(self.project.webhook_notifications.all().count(), 0)
+
+    def test_emailhookform(self):
+        self.assertEqual(self.project.emailhook_notifications.all().count(), 0)
+
+        data = {
+            'email': 'test@email.com'
+        }
+        form = EmailHookForm(data=data, project=self.project)
+        self.assertTrue(form.is_valid())
+        form.save()
+        self.assertEqual(self.project.emailhook_notifications.all().count(), 1)
+
+    def test_wrong_inputs_in_emailhookform(self):
+        self.assertEqual(self.project.emailhook_notifications.all().count(), 0)
+
+        data = {
+            'email': 'wrong_email@'
+        }
+        form = EmailHookForm(data=data, project=self.project)
+        self.assertFalse(form.is_valid())
+        self.assertDictEqual(form.errors, {'email': ['Enter a valid email address.']})
+        self.assertEqual(self.project.emailhook_notifications.all().count(), 0)
+
+        data = {
+            'email': ''
+        }
+        form = EmailHookForm(data=data, project=self.project)
+        self.assertFalse(form.is_valid())
+        self.assertDictEqual(form.errors, {'email': ['This field is required.']})
+        self.assertEqual(self.project.emailhook_notifications.all().count(), 0)
+
+
 class TestProjectEnvironmentVariablesForm(TestCase):
 
     def setUp(self):