Skip to content

Commit

Permalink
Merge pull request #390 from GhostManager/hotfix/api-enhancements
Browse files Browse the repository at this point in the history
Hotfix: API Enhancements
  • Loading branch information
chrismaddalena authored Feb 13, 2024
2 parents d84d112 + 3b5c11d commit 8e891a1
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 58 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ 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).

## [4.0.8] - 13 February 2024

### Added

* Added GraphQL events to update `deadline` and `markedComplete` fields for project objectives and tasks when these objects are updated via the GraphQL API
* Added a `filter_tags` filter to the reporting engine to allow for filtering findings and other models by their tags

### Fixed

* Fixed an issue with the template linter that could cause an error when retrieving undeclared variables under certain conditions

### Changed

* Changed the `user` relationship for `objective` to `assignedTo` in the GraphQL schema to better reflect the relationship between objectives and users

## [4.0.7] - 31 January 2024

### Fixed
Expand Down
Binary file modified DOCS/sample_reports/template.docx
Binary file not shown.
4 changes: 2 additions & 2 deletions VERSION
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
v4.0.7
31 January 2024
v4.0.8
13 February 2024
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__ = "4.0.7"
__version__ = "4.0.8"
VERSION = __version__
RELEASE_DATE = "31 January 2024"
RELEASE_DATE = "13 February 2024"

ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
APPS_DIR = ROOT_DIR / "ghostwriter"
Expand Down
149 changes: 149 additions & 0 deletions ghostwriter/api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
OplogEntryFactory,
ProjectContactFactory,
ProjectAssignmentFactory,
ProjectObjectiveFactory,
ProjectFactory,
ReportFactory,
ReportFindingLinkFactory,
Expand All @@ -36,6 +37,7 @@
SeverityFactory,
StaticServerFactory,
UserFactory,
ProjectSubtaskFactory,
)

logging.disable(logging.CRITICAL)
Expand Down Expand Up @@ -1721,6 +1723,153 @@ def test_graphql_projectcontact_update_event(self):
self.assertTrue(self.other_contact.primary)


class GraphqlProjectObjectiveUpdateEventTests(TestCase):
"""Collection of tests for :view:`api:GraphqlProjectObjectiveUpdateEvent`."""

@classmethod
def setUpTestData(cls):
cls.user = UserFactory(password=PASSWORD)
cls.uri = reverse("api:graphql_projectobjective_update_event")

cls.project = ProjectFactory()
cls.objective = ProjectObjectiveFactory(project=cls.project, complete=False)
cls.complete_data = {
"event": {
"data": {
"new": {"id": cls.objective.id, "complete": False, "deadline": cls.objective.deadline},
"old": {"id": cls.objective.id, "complete": True, "deadline": cls.objective.deadline},
},
}
}
cls.incomplete_data = {
"event": {
"data": {
"new": {"id": cls.objective.id, "complete": True, "deadline": cls.objective.deadline},
"old": {"id": cls.objective.id, "complete": False, "deadline": cls.objective.deadline},
},
}
}

def setUp(self):
self.client = Client()

def test_graphql_projectobjective_update_event(self):
self.objective.complete = True
self.objective.save()

response = self.client.post(
self.uri,
content_type="application/json",
data=self.complete_data,
**{
"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}",
},
)

self.assertEqual(response.status_code, 200)

self.objective.refresh_from_db()
self.assertTrue(self.objective.complete)
self.assertEqual(self.objective.marked_complete, date.today())

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

subtask = ProjectSubtaskFactory(
complete=False,
parent=self.objective,
deadline=self.objective.deadline + timedelta(days=1),
)
self.assertEqual(subtask.deadline, self.objective.deadline + timedelta(days=1))

response = self.client.post(
self.uri,
content_type="application/json",
data=self.incomplete_data,
**{
"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}",
},
)

self.assertEqual(response.status_code, 200)

self.objective.refresh_from_db()
self.assertFalse(self.objective.complete)
self.assertFalse(self.objective.marked_complete)

subtask.refresh_from_db()
self.assertEqual(subtask.deadline, self.objective.deadline)


class GraphqlProjectSubTaskUpdateEventTests(TestCase):
"""Collection of tests for :view:`api:GraphqlProjectSubTaskUpdateEvent`."""

@classmethod
def setUpTestData(cls):
cls.user = UserFactory(password=PASSWORD)
cls.uri = reverse("api:graphql_projectsubtaske_update_event")

cls.task = ProjectSubtaskFactory(complete=False)
cls.complete_data = {
"event": {
"data": {
"new": {"id": cls.task.id, "complete": False, "deadline": cls.task.deadline},
"old": {"id": cls.task.id, "complete": True, "deadline": cls.task.deadline},
},
}
}
cls.incomplete_data = {
"event": {
"data": {
"new": {"id": cls.task.id, "complete": True, "deadline": cls.task.deadline},
"old": {"id": cls.task.id, "complete": False, "deadline": cls.task.deadline},
},
}
}

def setUp(self):
self.client = Client()

def test_graphql_projectsubtask_update_event(self):
self.task.complete = True
self.task.deadline = self.task.parent.deadline + timedelta(days=1)
self.task.save()

response = self.client.post(
self.uri,
content_type="application/json",
data=self.complete_data,
**{
"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}",
},
)

self.assertEqual(response.status_code, 200)

self.task.refresh_from_db()
self.assertTrue(self.task.complete)
self.assertEqual(self.task.marked_complete, date.today())
self.assertEqual(self.task.deadline, self.task.parent.deadline)

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

response = self.client.post(
self.uri,
content_type="application/json",
data=self.incomplete_data,
**{
"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}",
},
)

self.assertEqual(response.status_code, 200)

self.task.refresh_from_db()
self.assertFalse(self.task.complete)
self.assertFalse(self.task.marked_complete)


# Tests related to CBVs for :model:`api:APIKey`


Expand Down
12 changes: 12 additions & 0 deletions ghostwriter/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@
GraphqlOplogEntryDeleteEvent,
GraphqlOplogEntryUpdateEvent,
GraphqlProjectContactUpdateEvent,
GraphqlProjectObjectiveUpdateEvent,
GraphqlProjectSubTaskUpdateEvent,
GraphqlReportFindingChangeEvent,
GraphqlReportFindingDeleteEvent,
GraphqlServerCheckoutDelete,
Expand Down Expand Up @@ -90,6 +92,16 @@
csrf_exempt(GraphqlProjectContactUpdateEvent.as_view()),
name="graphql_projectcontact_update_event",
),
path(
"event/projectobjective/update",
csrf_exempt(GraphqlProjectObjectiveUpdateEvent.as_view()),
name="graphql_projectobjective_update_event",
),
path(
"event/projectsubtask/update",
csrf_exempt(GraphqlProjectSubTaskUpdateEvent.as_view()),
name="graphql_projectsubtaske_update_event",
),
path("ajax/token/revoke/<int:pk>", ApiKeyRevoke.as_view(), name="ajax_revoke_token"),
path("token/create", ApiKeyCreate.as_view(), name="ajax_create_token"),
]
53 changes: 52 additions & 1 deletion ghostwriter/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@
ReportTemplate,
)
from ghostwriter.reporting.views import get_position
from ghostwriter.rolodex.models import Project, ProjectContact
from ghostwriter.rolodex.models import (
Project,
ProjectContact,
ProjectObjective,
ProjectSubTask,
)
from ghostwriter.shepherd.models import (
ActivityType,
Domain,
Expand Down Expand Up @@ -882,6 +887,52 @@ def post(self, request, *args, **kwargs):
return JsonResponse(self.data, status=self.status)


class GraphqlProjectObjectiveUpdateEvent(HasuraEventView):
"""
Event webhook to make database updates when :model:`rolodex.ProjectObjective`
entries change.
"""

def post(self, request, *args, **kwargs):
initial_deadline = self.old_data["deadline"]
instance = ProjectObjective.objects.get(id=self.new_data["id"])

subtasks = ProjectSubTask.objects.filter(parent=instance)
for task in subtasks:
if task.deadline > instance.deadline or task.deadline == initial_deadline:
task.deadline = instance.deadline
task.save()

if instance.complete:
instance.marked_complete = date.today()
else:
instance.marked_complete = None
instance.save()

return JsonResponse(self.data, status=self.status)


class GraphqlProjectSubTaskUpdateEvent(HasuraEventView):
"""
Event webhook to make database updates when :model:`rolodex.ProjectSubTask`
entries change.
"""

def post(self, request, *args, **kwargs):
instance = ProjectSubTask.objects.select_related("parent").get(id=self.new_data["id"])
if instance.deadline > instance.parent.deadline:
instance.deadline = instance.parent.deadline
instance.save()

if instance.complete:
instance.marked_complete = date.today()
else:
instance.marked_complete = None
instance.save()

return JsonResponse(self.data, status=self.status)


##################
# AJAX Functions #
##################
Expand Down
Loading

0 comments on commit 8e891a1

Please sign in to comment.