Skip to content

Commit

Permalink
Merge pull request #254 from GhostManager/hotfix/mute-log-notifications
Browse files Browse the repository at this point in the history
Hotfix, 3.0.7: Mute log notifications
  • Loading branch information
chrismaddalena authored Oct 10, 2022
2 parents fbe9799 + 195f7dd commit 5011542
Show file tree
Hide file tree
Showing 17 changed files with 325 additions and 24 deletions.
17 changes: 16 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,22 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [v3.0.5] - 3 October 2022
## [3.0.7] - 10 October 2022

### Fixed

* Fixed evidence files with uppercase extensions not being included in rendered reports (Closes #74)

### Added

* Logs now have an option to mute notifications (available to users with the `admin` and `manager` roles)

### Changed

* Log activity monitor will now only check logs for projects inside the execution window
* Tweaked report template permissions to allow users with the `admin` role that are not flagged as `staff` to edit or delete protected templates

## [v3.0.6] - 3 October 2022

### Added

Expand Down
4 changes: 2 additions & 2 deletions VERSION
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
v3.0.6
03 October 2022
v3.0.7
10 October 2022
4 changes: 2 additions & 2 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
# 3rd Party Libraries
import environ

__version__ = "3.0.6"
__version__ = "3.0.7"
VERSION = __version__
RELEASE_DATE = "03 October 2022"
RELEASE_DATE = "10 October 2022"

ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
APPS_DIR = ROOT_DIR / "ghostwriter"
Expand Down
6 changes: 5 additions & 1 deletion ghostwriter/modules/oplog_monitors.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,12 @@ def review_active_logs(hours: int = 24) -> dict:
if yesterday.weekday() < 5:
slack = SlackNotification()
active_logs = Oplog.objects.select_related("project").filter(
Q(project__complete=False) & Q(project__end_date__gte=today)
Q(project__complete=False)
& Q(project__end_date__gte=today)
& Q(project__start_date__lte=today)
& Q(mute_notifications=False)
)
logger.warning(active_logs)
for log in active_logs:
# Check if the latest log entry is older than the ``hours`` parameter
inactive = False
Expand Down
2 changes: 1 addition & 1 deletion ghostwriter/modules/reportwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -653,7 +653,7 @@ def process_evidence(self, evidence, par):
Paragraph meant to hold the evidence
"""
file_path = settings.MEDIA_ROOT + "/" + evidence["path"]
extension = file_path.split(".")[-1]
extension = file_path.split(".")[-1].lower()

# First, check if the file still exists on disk
if os.path.exists(file_path):
Expand Down
22 changes: 22 additions & 0 deletions ghostwriter/oplog/migrations/0006_oplog_mute_notifications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Generated by Django 3.2.11 on 2022-10-07 18:50

# Django Imports
from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
('oplog', '0005_auto_20220629_1802'),
]

operations = [
migrations.AddField(
model_name='oplog',
name='mute_notifications',
field=models.BooleanField(default=False, help_text='Mute activity monitoring notifications for this log'),
),
migrations.RunSQL(
'ALTER TABLE oplog_oplog ALTER COLUMN mute_notifications SET DEFAULT FALSE;',
)
]
4 changes: 4 additions & 0 deletions ghostwriter/oplog/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class Oplog(models.Model):
null=True,
help_text="Select the project that will own this oplog",
)
mute_notifications = models.BooleanField(
default=False,
help_text="Mute activity monitoring notifications for this log",
)

class Meta:
unique_together = ["name", "project"]
Expand Down
55 changes: 55 additions & 0 deletions ghostwriter/oplog/templates/oplog/entries_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,21 @@
<a href="javascript:void(0)" id="importNewEntries" class="dropdown-item icon upload-icon">Import Entries</a>
<a href="javascript:void(0)" id="exportEntries" class="dropdown-item icon export-icon">Export Entries</a>
<a href="{% url 'rolodex:project_detail' project.id %}" class="dropdown-item icon project-icon" >Jump to Project</a>
{% if request.user.is_staff or request.user.role == "manager" or request.user.role == "admin" %}
<a
href="javascript:void(0)"
class="dropdown-item js-toggle-mute icon {% if oplog.mute_notifications %}silenced-notification-icon{% else %}notification-bell-icon{% endif %}"
toggle-mute-csrftoken="{{ csrf_token }}"
toggle-mute-url="{% url 'oplog:ajax_oplog_mute_toggle' oplog.id %}"
toggle-mute-id="{{ oplog.id }}"
>
{% if oplog.mute_notifications %}
Notifications: Off
{% else %}
Notifications: On
{% endif %}
</a>
{% endif %}
</div>
</div>
</div>
Expand Down Expand Up @@ -606,6 +621,46 @@ <h3><i class="fa fa-sync fa-spin"></i> Loading the log entries...</h3>
download(`/oplog/api/entries?export=csv&&oplog_id={{ pk }}`, filename);
})
});
</script>

<!-- Toggle Project Status with AJAX -->
<script>
$('.js-toggle-mute').click(function () {
var toggleLink = $(this);
var url = $(this).attr('toggle-mute-url');
var oplogId = $(this).attr('toggle-mute-id');
var csrftoken = $(this).attr('toggle-mute-csrftoken');
// Prep AJAX request with CSRF token
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader('X-CSRFToken', csrftoken);
}
}
});
// Send AJAX POST request
$.ajax({
url: url,
type: 'POST',
dataType: 'json',
data: {
'oplog': oplogId
},
success: function (data) {
if (data['toggle']) {
toggleLink.removeClass('notification-bell-icon')
toggleLink.addClass('silenced-notification-icon')
toggleLink.text('Notifications: Off')
} else {
toggleLink.removeClass('silenced-notification-icon')
toggleLink.addClass('notification-bell-icon')
toggleLink.text('Notifications: On')
}
if (data['message']) {
displayToastTop({type:data['result'], string:data['message'], title:'Oplog Update'});
}
},
});
});
</script>
{% endblock %}
15 changes: 15 additions & 0 deletions ghostwriter/oplog/templates/oplog/oplog_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,14 @@ <h4 style="margin-top: 0;" class="alert-heading">Hey You!</h4>
<th class="align-middle">ID</th>
<th class="align-middle">Name</th>
<th class="align-middle">Project</th>
<th class="align-middle">
<div class="dropdown dropleft">
<span id="notification-info-btn" class="dropdown-info mr-2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Notifications</span>
<div id="notification-info" class="dropdown-menu dropdown-info-content" aria-labelledby="notification-info-btn">
<p>Notification status determines if Ghostwriter will send notifications for the log when the "Review Active Logs" task is run.</p>
</div>
</div>
</th>
<th class="sorter-false align-middle">Export CSV</th>
</tr>
</thead>
Expand All @@ -45,6 +53,13 @@ <h4 style="margin-top: 0;" class="alert-heading">Hey You!</h4>
<td class="align-middle">{{ log.id }}</td>
<td class="align-middle"><a class="clickable" href="{% url 'oplog:oplog_entries' log.pk %}">{{ log.name }}</a></td>
<td class="align-middle">{{ log.project.client }} {{ log.project.project_type }} ({{ log.project.start_date }})</td>
<td class="align-middle">
{% if log.mute_notifications %}
<span class="icon silenced-notification-icon">Silenced</span>
{% else %}
<span class="icon notification-bell-icon">Active</span>
{% endif %}
</td>
<td class="align-middle js-export-oplog" oplog-id="{{ log.pk }}" oplog-name="{{ log.name }}"><a href="javascript:void(0);" class="btn btn-secondary col-md-12">Export</a></td>
</tr>
{% endfor %}
Expand Down
94 changes: 94 additions & 0 deletions ghostwriter/oplog/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Django Imports
from django.test import Client, TestCase
from django.urls import reverse
from django.utils.encoding import force_str

# Ghostwriter Libraries
from ghostwriter.factories import (
Expand Down Expand Up @@ -264,3 +265,96 @@ def test_form_with_no_active_projects(self):

self.project.complete = False
self.project.save()


class OplogMuteToggleViewTests(TestCase):
"""Collection of tests for :view:`oplog.OplogMuteToggle`."""

@classmethod
def setUpTestData(cls):
cls.log = OplogFactory()
cls.user = UserFactory(password=PASSWORD)
cls.mgr_user = UserFactory(password=PASSWORD, role="manager")
cls.admin_user = UserFactory(password=PASSWORD, role="admin")
cls.staff_user = UserFactory(password=PASSWORD, is_staff=True)
cls.uri = reverse("oplog:ajax_oplog_mute_toggle", kwargs={"pk": cls.log.pk})

def setUp(self):
self.client = Client()
self.client_auth = Client()
self.client_mgr = Client()
self.client_admin = Client()
self.client_staff = Client()
self.client_auth.login(username=self.user.username, password=PASSWORD)
self.assertTrue(
self.client_auth.login(username=self.user.username, password=PASSWORD)
)
self.client_mgr.login(username=self.mgr_user.username, password=PASSWORD)
self.assertTrue(
self.client_admin.login(username=self.mgr_user.username, password=PASSWORD)
)
self.client_admin.login(username=self.admin_user.username, password=PASSWORD)
self.assertTrue(
self.client_admin.login(username=self.admin_user.username, password=PASSWORD)
)
self.client_staff.login(username=self.staff_user.username, password=PASSWORD)
self.assertTrue(
self.client_staff.login(username=self.staff_user.username, password=PASSWORD)
)

def test_view_uri_exists_at_desired_location(self):
data = {
"result": "success",
"message": "Oplog monitor notifications have been muted",
"toggle": 1,
}
self.log.mute_notifications = False
self.log.save()

response = self.client_staff.post(self.uri)
self.assertEqual(response.status_code, 200)
self.assertJSONEqual(force_str(response.content), data)

self.log.refresh_from_db()
self.assertEqual(self.log.mute_notifications, True)

data = {
"result": "success",
"message": "Oplog monitor notifications have been unmuted",
"toggle": 0,
}
response = self.client_staff.post(self.uri)
self.assertJSONEqual(force_str(response.content), data)

self.log.refresh_from_db()
self.assertEqual(self.log.mute_notifications, False)

def test_view_requires_login(self):
response = self.client.post(self.uri)
self.assertEqual(response.status_code, 403)
data = {
"result": "error",
"message": "You must be logged in",
}
self.assertJSONEqual(force_str(response.content), data)

def test_view_permissions(self):
response = self.client_auth.post(self.uri)
self.assertEqual(response.status_code, 403)
data = {
"result": "error",
"message": "Only a manager or admin can mute notifications",
}
self.assertJSONEqual(force_str(response.content), data)

response = self.client_mgr.post(self.uri)
self.assertEqual(response.status_code, 200)
self.assertIn("success", force_str(response.content))

response = self.client_admin.post(self.uri)
self.assertEqual(response.status_code, 200)
self.assertIn("success", force_str(response.content))

response = self.client_staff.post(self.uri)
self.assertEqual(response.status_code, 200)
self.assertIn("success", force_str(response.content))
8 changes: 8 additions & 0 deletions ghostwriter/oplog/urls.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
"""This contains all of the URL mappings used by the Oplog application."""


# Django Imports
from django.urls import include, path

# 3rd Party Libraries
from rest_framework import routers

from . import views
Expand Down Expand Up @@ -35,3 +38,8 @@
path("<int:pk>/entries", views.OplogListEntries, name="oplog_entries"),
path("import", views.OplogEntriesImport, name="oplog_import"),
]

# URLs for AJAX requests
urlpatterns += [
path("ajax/oplog/mute/<int:pk>", views.OplogMuteToggle.as_view(), name="ajax_oplog_mute_toggle"),
]
Loading

0 comments on commit 5011542

Please sign in to comment.