diff --git a/ghostwriter/modules/notifications_slack.py b/ghostwriter/modules/notifications_slack.py index 7be3db903..1d1607766 100644 --- a/ghostwriter/modules/notifications_slack.py +++ b/ghostwriter/modules/notifications_slack.py @@ -283,6 +283,7 @@ def craft_burned_msg( self, domain: str, categories: str, + scanners: str, burned_explanation: str, ) -> list: """ @@ -316,6 +317,10 @@ def craft_burned_msg( "type": "mrkdwn", "text": f"*Categories:*\n{categories}", }, + { + "type": "mrkdwn", + "text": f"*Flagged as Malicious By:*\n{scanners}", + }, ], }, { diff --git a/ghostwriter/modules/review.py b/ghostwriter/modules/review.py index 1ad05d6f5..001c65e1d 100644 --- a/ghostwriter/modules/review.py +++ b/ghostwriter/modules/review.py @@ -152,6 +152,7 @@ def check_domain_status(self): # Ignore any expired domains because we don't control them anymore if domain.is_expired() is False: domain_categories = {} + malicious_scans = [] bad_categories = [] burned_explanations = [] lab_results[domain.id] = {} @@ -216,10 +217,19 @@ def check_domain_status(self): if "last_analysis_stats" in vt_results["data"]: analysis_stats = vt_results["data"]["last_analysis_stats"] if analysis_stats["malicious"] > 0: + for scanner, result in vt_results["data"]["last_analysis_results"].items(): + if result["result"] == "malicious": + malicious_scans.append(scanner) burned = True - burned_explanations.append("A VirusTotal scanner has flagged the domain as malicious.") + burned_explanations.append( + "{} VirusTotal scanner(s) ({}) flagged the domain as malicious.".format( + analysis_stats["malicious"], + ", ".join(malicious_scans), + ) + ) logger.warning( - "A VirusTotal scanner has flagged the %s as malicious", + "%s VirusTotal scanners flagged the %s as malicious", + analysis_stats["malicious"], domain_name, ) @@ -234,17 +244,18 @@ def check_domain_status(self): ) ) logger.warning( - "There are %s VirusTotal community votes flagging the the domain as malicious", + "There are %s VirusTotal community votes flagging the the domain as malicious.", votes["malicious"], ) else: lab_results[domain.id]["vt_results"] = "none" - logger.warning("Did not receive results for %s from VirusTotal", domain_name) + logger.warning("Did not receive results for %s from VirusTotal.", domain_name) # Assemble the dictionary to return for this domain lab_results[domain.id]["burned"] = burned lab_results[domain.id]["categories"] = domain_categories + lab_results[domain.id]["scanners"] = malicious_scans lab_results[domain.id]["warnings"]["messages"] = warnings lab_results[domain.id]["warnings"]["total"] = len(warnings) if burned: diff --git a/ghostwriter/rolodex/templates/rolodex/project_detail.html b/ghostwriter/rolodex/templates/rolodex/project_detail.html index a374e3782..d7d3d43c5 100644 --- a/ghostwriter/rolodex/templates/rolodex/project_detail.html +++ b/ghostwriter/rolodex/templates/rolodex/project_detail.html @@ -1870,50 +1870,6 @@

Project Notes

prepareTodoList(); }); - - {% comment %} Create a new project point of contact from a client contact {% endcomment %} - $(document).ready(function () { - $('#id_client_contact').change(function () { - let option =$(this).val(); - if(option === -1) { - $('#assign-btn').prop('disabled', true); - } else { - $('#assign-btn').prop('disabled',false); - } - }); - $('#assign-project-contact-form').submit(function (e) { - e.preventDefault(); - let url = $(this).attr('action'); - let $contact = $('#id_client_contact') - let contactId = $contact.val(); - let csrftoken = $('input[name=csrfmiddlewaretoken]').val(); - if ($contact.val() === '') { - contactId = -1 - } - $.ajaxSetup({ - beforeSend: function (xhr, settings) { - if (!csrfSafeMethod(settings.type) && !this.crossDomain) { - xhr.setRequestHeader('X-CSRFToken', csrftoken); - } - } - }); - $.ajax({ - url: url, - type: 'POST', - dataType: 'json', - data: { - 'contact': contactId, - 'csrfmiddlewaretoken': csrftoken, - }, - success: function (data) { - if (data['message']) { - displayToastTop({type: data['result'], string: data['message'], title: 'Contacts Update'}); - } - update_project_contacts(); - }, - }); - }); - }); {% comment %} Generate modals for displaying the scope lists {% endcomment %} diff --git a/ghostwriter/rolodex/templates/snippets/project_contacts_table.html b/ghostwriter/rolodex/templates/snippets/project_contacts_table.html index 06dbf7de5..df6e69a3c 100644 --- a/ghostwriter/rolodex/templates/snippets/project_contacts_table.html +++ b/ghostwriter/rolodex/templates/snippets/project_contacts_table.html @@ -91,3 +91,49 @@

Project Points of Contacts

+ + diff --git a/ghostwriter/rolodex/tests/test_views.py b/ghostwriter/rolodex/tests/test_views.py index da0276483..0ba83f79f 100644 --- a/ghostwriter/rolodex/tests/test_views.py +++ b/ghostwriter/rolodex/tests/test_views.py @@ -828,3 +828,38 @@ def test_invalid_contact_id(self): response = self.client_mgr.post(self.uri, {"contact": -1}) self.assertEqual(response.status_code, 200) self.assertJSONEqual(force_str(response.content), data) + + +class ProjectDetailViewTests(TestCase): + """Collection of tests for :view:`rolodex.ProjectDetailView`.""" + + @classmethod + def setUpTestData(cls): + cls.user = UserFactory(password=PASSWORD) + cls.user_mgr = UserFactory(password=PASSWORD, role="manager") + cls.project = ProjectFactory() + cls.uri = reverse("rolodex:project_detail", kwargs={"pk": cls.project.pk}) + + def setUp(self): + self.client = Client() + self.client_auth = Client() + self.client_mgr = Client() + self.assertTrue(self.client_auth.login(username=self.user.username, password=PASSWORD)) + self.assertTrue(self.client_mgr.login(username=self.user_mgr.username, password=PASSWORD)) + + def test_view_uri_exists_at_desired_location(self): + response = self.client_mgr.get(self.uri) + self.assertEqual(response.status_code, 200) + + def test_view_requires_login_and_permissions(self): + response = self.client.get(self.uri) + self.assertEqual(response.status_code, 302) + + response = self.client_mgr.get(self.uri) + self.assertEqual(response.status_code, 200) + + response = self.client_auth.get(self.uri) + self.assertEqual(response.status_code, 302) + ProjectAssignmentFactory(project=self.project, operator=self.user) + response = self.client_auth.get(self.uri) + self.assertEqual(response.status_code, 200) diff --git a/ghostwriter/rolodex/views.py b/ghostwriter/rolodex/views.py index 31ffaf888..c032cf97a 100644 --- a/ghostwriter/rolodex/views.py +++ b/ghostwriter/rolodex/views.py @@ -134,9 +134,22 @@ def update_project_contacts(request, pk): if not verify_access(request.user, project_instance): return ForbiddenJsonResponse() + contacts = ClientContact.objects.filter(client=project_instance.client) + for contact in contacts: + if ( + ProjectContact.objects.filter( + name=contact.name, + email=contact.email, + phone=contact.phone, + project=project_instance, + ).count() + > 0 + ): + contacts = contacts.exclude(id=contact.id) + html = render_to_string( "snippets/project_contacts_table.html", - {"project": project_instance}, + {"project": project_instance, "client_contacts": contacts}, ) return HttpResponse(html) @@ -1439,24 +1452,6 @@ def handle_no_permission(self): messages.error(self.request, "You do not have permission to access that.") return redirect("home:dashboard") - def get_context_data(self, **kwargs): - ctx = super().get_context_data(**kwargs) - - contacts = ClientContact.objects.filter(client=self.object.client) - for contact in contacts: - if ( - ProjectContact.objects.filter( - name=contact.name, - email=contact.email, - phone=contact.phone, - project=self.object, - ).count() - > 0 - ): - contacts = contacts.exclude(id=contact.id) - ctx["client_contacts"] = contacts - return ctx - class ProjectCreate(RoleBasedAccessControlMixin, CreateView): """ diff --git a/ghostwriter/shepherd/tasks.py b/ghostwriter/shepherd/tasks.py index fdaa7702f..b0be071ae 100644 --- a/ghostwriter/shepherd/tasks.py +++ b/ghostwriter/shepherd/tasks.py @@ -193,8 +193,9 @@ def release_domains(no_action=False): # Check if tomorrow is the end date if date.today() == warning_date: release_me = False - message = "Your domain, {}, will be released tomorrow! Modify the project's end date as needed.".format( - domain.name + message = "Reminder: your project is ending soon and your domain, {}, will be released when it does. If your project is still ending after EOB on {}, you don't need to do anything!".format( + domain.name, + release_date, ) if slack.enabled: err = slack.send_msg(message, slack_channel) @@ -392,10 +393,19 @@ def check_domains(domain_id=None): if lab_results[k]["burned"]: domain_qs.health_status = HealthStatus.objects.get(health_status="Burned") change = "burned" + pretty_categories = [] + for vendor, category in lab_results[k]["categories"].items(): + pretty_categories.append(f"{vendor}: {category}") + + scanners = "N/A" + if lab_results[k]["scanners"]: + scanners = "\n".join(lab_results[k]["scanners"]) + if slack.enabled: blocks = slack.craft_burned_msg( v["domain"], - lab_results[k]["categories"], + "\n".join(pretty_categories), + scanners, lab_results[k]["burned_explanation"], ) if slack.enabled: