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: