Skip to content

Commit

Permalink
Merge pull request #251 from GhostManager/release/v3.0.5
Browse files Browse the repository at this point in the history
Release: v3.0.5
  • Loading branch information
chrismaddalena authored Sep 23, 2022
2 parents e9f00f4 + 5e5819a commit 3e896bf
Show file tree
Hide file tree
Showing 18 changed files with 424 additions and 171 deletions.
14 changes: 10 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ 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).

## [Unreleased] - 13 September 2022
## [v3.0.5] - 23 September 2022

### Fixed

* Fixed finding guidance display when viewing a finding attached to a report
* Fixed connection errors with an AWS region causing remaining regions to be skipped in the cloud monitoring task

### Added

* Added an `attachFinding` mutation to the GraphQL API for easily attaching a copy of a finding from the library to a report
* Added ability to copy/paste evidence into the upload form and view a preview (Thanks to @brandonscholet! Closes PR #228)

## [3.0.4] - 12 September 2022
Expand Down Expand Up @@ -113,7 +119,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Removed

* Removed old environmnt variable templates from the project because they are no longer used for setup or management
* Removed old environment variable templates from the project because they are no longer used for setup or management

## [2.3.0-rc2] - 3 June 2022

Expand Down Expand Up @@ -247,7 +253,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Changed default display filter to only show active projects
* Adjusted project status filter to have three options: all projects, active projects, and completed projects
* Updated dashboard and calendar to show past and current events for browsing history within the calendar
* Past events marked as completed will appear dimed with a strikethrough and `: Complete` added to the end
* Past events marked as completed will appear dimmed with a strike-through and `: Complete` added to the end
* Upgraded dependencies to their latest versions (where possible)
* Django v3.1.13 -> v3.2.11
* Did not upgrade `docxtpl`
Expand Down Expand Up @@ -501,7 +507,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* Adjusted Dockerfile files to fix potential filesystem issues with the latest Alpine Linux image \(submitted by @studebacon with PR \#143\).
* Added a missing field in the Report Template admin panel
* "Add to Report" on the finding details page now works
* Updated delete actions for operation logs to avoid anerror that could prevent the deletion of entries when deleting an entire log
* Updated delete actions for operation logs to avoid an error that could prevent the deletion of entries when deleting an entire log
* Domain age calculations are now accurate
* An invalid value for domain purchase date no longer causes a server error during validation
* Constrained `Twisted` library to v20.3.0 to fix a potential issue that could come up with Django Channels
Expand Down
4 changes: 2 additions & 2 deletions VERSION
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
3.0.4
12 September 2022
3.0.5
23 September 2022
2 changes: 1 addition & 1 deletion compose/production/hasura/Dockerfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
FROM hasura/graphql-engine:v2.7.0.cli-migrations-v3
FROM hasura/graphql-engine:v2.12.0.cli-migrations-v3
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.4"
__version__ = "3.0.5"
VERSION = __version__
RELEASE_DATE = "12 September 2022"
RELEASE_DATE = "23 September 2022"

ROOT_DIR = Path(__file__).resolve(strict=True).parent.parent.parent
APPS_DIR = ROOT_DIR / "ghostwriter"
Expand Down
83 changes: 82 additions & 1 deletion ghostwriter/api/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
DomainFactory,
DomainStatusFactory,
EvidenceFactory,
FindingFactory,
HistoryFactory,
OplogEntryFactory,
ProjectAssignmentFactory,
Expand Down Expand Up @@ -285,7 +286,7 @@ def test_with_invalid_json(self):
self.assertJSONEqual(force_str(response.content), result)


# Tests related to theauthetnication webhook
# Tests related to the authentication webhook


class HasuraWebhookTests(TestCase):
Expand Down Expand Up @@ -976,6 +977,86 @@ def test_deleting_protected_template_without_access(self):
self.assertEqual(response.status_code, 401)


class GraphqlAttachFindingAction(TestCase):
"""Collection of tests for :view:`GraphqlAttachFinding`."""

@classmethod
def setUpTestData(cls):
cls.ReportFindingLink = ReportFindingLinkFactory._meta.model

cls.user = UserFactory(password=PASSWORD)
cls.other_user = UserFactory(password=PASSWORD)
cls.mgr_user = UserFactory(password=PASSWORD, role="manager")
cls.uri = reverse("api:graphql_attach_finding")

cls.project = ProjectFactory()
cls.report = ReportFactory(project=cls.project)
cls.finding = FindingFactory()
_ = ProjectAssignmentFactory(project=cls.project, operator=cls.user)

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

def generate_data(self, finding_id, report_id):
return {"input": {"findingId": finding_id, "reportId": report_id, }}

def test_attaching_finding(self):
_, token = utils.generate_jwt(self.user)
response = self.client.post(
self.uri,
data=self.generate_data(self.finding.id, self.report.id),
content_type="application/json",
**{"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}", "HTTP_AUTHORIZATION": f"Bearer {token}"},
)
self.assertEqual(response.status_code, 200)
new_finding = response.json()["id"]
self.assertTrue(self.ReportFindingLink.objects.filter(id=new_finding).exists())

def test_attaching_finding_with_invalid_report(self):
_, token = utils.generate_jwt(self.user)
response = self.client.post(
self.uri,
data=self.generate_data(self.finding.id, 999),
content_type="application/json",
**{"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}", "HTTP_AUTHORIZATION": f"Bearer {token}"},
)
self.assertEqual(response.status_code, 400)
data = {"message": "Report does not exist", "extensions": {"code": "ReportDoesNotExist"}}
self.assertJSONEqual(force_str(response.content), data)

def test_attaching_finding_with_invalid_finding(self):
_, token = utils.generate_jwt(self.user)
response = self.client.post(
self.uri,
data=self.generate_data(999, self.report.id),
content_type="application/json",
**{"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}", "HTTP_AUTHORIZATION": f"Bearer {token}"},
)
self.assertEqual(response.status_code, 400)
data = {"message": "Finding does not exist", "extensions": {"code": "FindingDoesNotExist"}}
self.assertJSONEqual(force_str(response.content), data)

def test_attaching_finding_with_mgr_access(self):
_, token = utils.generate_jwt(self.mgr_user)
response = self.client.post(
self.uri,
data=self.generate_data(self.finding.id, self.report.id),
content_type="application/json",
**{"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}", "HTTP_AUTHORIZATION": f"Bearer {token}"},
)
self.assertEqual(response.status_code, 200)

def test_attaching_finding_without_access(self):
_, token = utils.generate_jwt(self.other_user)
response = self.client.post(
self.uri,
data=self.generate_data(self.finding.id, self.report.id),
content_type="application/json",
**{"HTTP_HASURA_ACTION_SECRET": f"{ACTION_SECRET}", "HTTP_AUTHORIZATION": f"Bearer {token}"},
)
self.assertEqual(response.status_code, 401)


# Tests related to Hasura Event Triggers


Expand Down
5 changes: 5 additions & 0 deletions ghostwriter/api/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ghostwriter.api.views import (
ApiKeyCreate,
ApiKeyRevoke,
GraphqlAttachFinding,
GraphqlAuthenticationWebhook,
GraphqlCheckoutDomain,
GraphqlCheckoutServer,
Expand All @@ -29,18 +30,22 @@
app_name = "api"

urlpatterns = [
# Actions
path("test", csrf_exempt(GraphqlTestView.as_view()), name="graphql_test"),
path("test_event", csrf_exempt(GraphqlEventTestView.as_view()), name="graphql_event_test"),
path("webhook", csrf_exempt(GraphqlAuthenticationWebhook.as_view()), name="graphql_webhook"),
path("login", csrf_exempt(GraphqlLoginAction.as_view()), name="graphql_login"),
path("whoami", csrf_exempt(GraphqlWhoami.as_view()), name="graphql_whoami"),
path("whoami", csrf_exempt(GraphqlWhoami.as_view()), name="graphql_whoami"),
path("generateReport", csrf_exempt(GraphqlGenerateReport.as_view()), name="graphql_generate_report"),
path("checkoutDomain", csrf_exempt(GraphqlCheckoutDomain.as_view()), name="graphql_checkout_domain"),
path("checkoutServer", csrf_exempt(GraphqlCheckoutServer.as_view()), name="graphql_checkout_server"),
path("deleteDomainCheckout", csrf_exempt(GraphqlDomainCheckoutDelete.as_view()), name="graphql_domain_checkout_delete"),
path("deleteServerCheckout", csrf_exempt(GraphqlServerCheckoutDelete.as_view()), name="graphql_server_checkout_delete"),
path("deleteEvidence", csrf_exempt(GraphqlDeleteEvidenceAction.as_view()), name="graphql_delete_evidence"),
path("deleteTemplate", csrf_exempt(GraphqlDeleteReportTemplateAction.as_view()), name="graphql_delete_template"),
path("attachFinding", csrf_exempt(GraphqlAttachFinding.as_view()), name="graphql_attach_finding"),
# Events
path("event/domain/update", csrf_exempt(GraphqlDomainUpdateEvent.as_view()), name="graphql_domain_update_event"),
path("event/oplogentry/create", csrf_exempt(GraphqlOplogEntryCreateEvent.as_view()), name="graphql_oplogentry_create_event"),
path("event/oplogentry/update", csrf_exempt(GraphqlOplogEntryUpdateEvent.as_view()), name="graphql_oplogentry_update_event"),
Expand Down
46 changes: 44 additions & 2 deletions ghostwriter/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,17 @@
from ghostwriter.api import utils
from ghostwriter.api.forms import ApiKeyForm
from ghostwriter.api.models import APIKey
from ghostwriter.modules.model_utils import to_dict
from ghostwriter.modules.reportwriter import Reportwriter
from ghostwriter.oplog.models import OplogEntry
from ghostwriter.reporting.models import Evidence, Report, ReportTemplate
from ghostwriter.reporting.models import (
Evidence,
Finding,
Report,
ReportFindingLink,
ReportTemplate,
)
from ghostwriter.reporting.views import get_position
from ghostwriter.rolodex.models import Project
from ghostwriter.shepherd.models import (
ActivityType,
Expand Down Expand Up @@ -162,7 +170,7 @@ def dispatch(self, request, *args, **kwargs):
utils.generate_hasura_error_payload("Missing all required inputs", "InvalidRequestBody"),
status=400
)
# Hasura checks for required values, but we check here in case of a discrepency between the GraphQL schema and the view
# Hasura checks for required values, but we check here in case of a discrepancy between the GraphQL schema and the view
for required_input in self.required_inputs:
if required_input not in self.input:
return JsonResponse(
Expand Down Expand Up @@ -606,6 +614,40 @@ def post(self, request, *args, **kwargs):
return JsonResponse(data, status=self.status)


class GraphqlAttachFinding(JwtRequiredMixin, HasuraActionView):
"""
Endpoint for attaching a :model:`reporting.Finding` to a :model:`reporting.Report`
as a new :model:`reporting.ReportFindingLink`.
"""
required_inputs = ["findingId", "reportId", ]

def post(self, request, *args, **kwargs):
finding_id = self.input["findingId"]
report_id = self.input["reportId"]
try:
report = Report.objects.get(id=report_id)
except Report.DoesNotExist:
return JsonResponse(utils.generate_hasura_error_payload("Report does not exist", "ReportDoesNotExist"), status=400)
try:
finding = Finding.objects.get(id=finding_id)
except Finding.DoesNotExist:
return JsonResponse(utils.generate_hasura_error_payload("Finding does not exist", "FindingDoesNotExist"), status=400)

if utils.verify_project_access(self.user_obj, report.project):
finding_dict = to_dict(finding, resolve_fk=True)
report_link = ReportFindingLink(
report=report,
assigned_to=self.user_obj,
position=get_position(report.id, finding.severity),
**finding_dict,
)
report_link.save()
data = {"id": report_link.pk,}
return JsonResponse(data, status=self.status)

return JsonResponse(utils.generate_hasura_error_payload("Unauthorized access", "Unauthorized"), status=401)


##########################
# Hasura Event Endpoints #
##########################
Expand Down
1 change: 0 additions & 1 deletion ghostwriter/home/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ def update_session(request):
"result": "success",
"message": "Session updated",
}
logger.info("Session updated for user %s", request.session["_auth_user_id"])
return JsonResponse(data)

return HttpResponseNotAllowed(["POST"])
Expand Down
Loading

0 comments on commit 3e896bf

Please sign in to comment.