Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions backend/.coveragerc
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
[run]
source = .
omit = frontend/*,*test*,*apps.py,*manage.py,*__init__.py,*migrations*,*asgi*,*wsgi*,*admin.py,*urls.py
omit = frontend/*,*test*,*migrations*,*asgi*,*wsgi*,*admin.py

[report]
exclude_lines =
pragma: no cover
omit = frontend/*,*test*,*apps.py,*manage.py,*__init__.py,*migrations*,*asgi*,*wsgi*,*admin.py,*urls.py
omit = frontend/*,*test*,*migrations*,*asgi*,*wsgi*,*admin.py
16 changes: 15 additions & 1 deletion backend/analyzer/test/test_custom_parser_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import pytest

from analyzer.services.custom_parser_classes import XMLParser, PlainTextParser
from analyzer.services.custom_parser_classes import XMLParser, PlainTextParser, CustomTextParser


@pytest.mark.django_db()
Expand All @@ -20,3 +20,17 @@ def test_plain_text_correct_parse(self, setup):
xml_parser = PlainTextParser()
assert self.body == xml_parser.parse(stream=self.stream)

def test_custom_text_parser(self, setup):
custom_parser = CustomTextParser()
assert self.body == custom_parser.parse(stream=self.stream)

def test_custom_text_parser_with_encoding(self, setup):
custom_parser = CustomTextParser()
parser_context = {"encoding": "utf-8"}
assert self.body == custom_parser.parse(stream=self.stream, parser_context=parser_context)

def test_custom_text_parser_parse_error(self, setup):
custom_parser = CustomTextParser()
invalid_stream = io.BytesIO(b"\x80\x81\x82")
with pytest.raises(ParseError):
custom_parser.parse(stream=invalid_stream)
18 changes: 18 additions & 0 deletions backend/analyzer/test/test_cve_fetcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,21 @@ def test_epss_score():

def test_vendor_reference():
assert len(cve_data["vendor_reference"]) >= 0


def test_find_vendor_reference():
references = [
{"url": "http://example.com", "tags": ["Vendor Advisory"]},
{"url": "http://example2.com", "tags": []}
]
vendor_reference = cve_fetcher._find_vendor_reference(references)
assert vendor_reference == "http://example.com"


def test_find_cwes():
weaknesses = [
{"description": [{"value": "CWE-79"}]},
{"description": [{"value": "CWE-89"}]}
]
cwes = cve_fetcher._find_cwes(weaknesses)
assert cwes == ["CWE-79", "CWE-89"]
40 changes: 40 additions & 0 deletions backend/analyzer/test/test_cve_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,43 @@ def test_update_multiple_objects(real_cve_object, dummy_cve_object):

assert 0 <= cve_objects[0].cvss <= 10
assert 0 <= cve_objects[1].cvss <= 10


@pytest.mark.django_db
def test_get_single_cve_object():
object_manager = CVEObjectManager(cve_id)
cve_object = object_manager.get()
assert isinstance(cve_object, CVEObject)
assert cve_object.cve_id == cve_id


@pytest.mark.django_db
def test_get_multiple_cve_objects():
object_manager = CVEObjectManager([cve_id, cve_id2])
cve_objects = object_manager.get()
assert isinstance(cve_objects, list)
assert len(cve_objects) == 2
assert cve_objects[0].cve_id == cve_id
assert cve_objects[1].cve_id == cve_id2


@pytest.mark.django_db
def test_update_cve_object_attributes(real_cve_object):
object_manager = CVEObjectManager(real_cve_object)
object_manager.update_cve()
cve_object = object_manager.get()
assert cve_object.cvss is not None
assert cve_object.epss is not None
assert cve_object.base_severity is not None
assert cve_object.published is not None
assert cve_object.updated is not None
assert cve_object.description is not None
assert cve_object.attack_vector is not None
assert cve_object.attack_complexity is not None
assert cve_object.privileges_required is not None
assert cve_object.user_interaction is not None
assert cve_object.confidentiality_impact is not None
assert cve_object.integrity_impact is not None
assert cve_object.availability_impact is not None
assert cve_object.scope is not None
assert cve_object.recommended_url is not None
39 changes: 39 additions & 0 deletions backend/analyzer/test/test_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,3 +163,42 @@ def test_cascade_deletion(self):
def test_delete_report(self):
Report.objects.get(dependency=self.dependency, cve_object=self.cve).delete()
self.assertEqual(0, len(Report.objects.all()))


class ProjectModelTestCase(TestCase):
def setUp(self):
self.project = Project.objects.create(project_id="TestProject")

def test_dependency_count(self):
Dependency.objects.create(dependency_name="test.py", project=self.project)
self.assertEqual(self.project.dependency_count, 1)

def test_resolved_report_count(self):
dependency = Dependency.objects.create(dependency_name="test.py", project=self.project)
cve = CVEObject.objects.create(cve_id="CVE2304-2333")
Report.objects.create(dependency=dependency, cve_object=cve, status=Status.NO_THREAT.name)
self.assertEqual(self.project.resolved_report_count, 1)

def test_solution_distribution(self):
dependency = Dependency.objects.create(dependency_name="test.py", project=self.project)
cve = CVEObject.objects.create(cve_id="CVE2304-2333")
Report.objects.create(dependency=dependency, cve_object=cve, status=Status.NO_THREAT.name, solution="Solution1")
self.assertEqual(self.project.solution_distribution, {"Solution1": 1})

def test_status_distribution(self):
dependency = Dependency.objects.create(dependency_name="test.py", project=self.project)
cve = CVEObject.objects.create(cve_id="CVE2304-2333")
Report.objects.create(dependency=dependency, cve_object=cve, status=Status.REVIEW.name)
self.assertEqual(self.project.status_distribution, {Status.REVIEW.name: 1})

def test_calculate_risk_score(self):
dependency = Dependency.objects.create(dependency_name="test.py", project=self.project)
cve = CVEObject.objects.create(cve_id="CVE2304-2333", epss=0.5)
Report.objects.create(dependency=dependency, cve_object=cve)
self.assertEqual(self.project.calculate_risk_score, 0.5)

def test_vulnerabilities_count(self):
dependency = Dependency.objects.create(dependency_name="test.py", project=self.project)
cve = CVEObject.objects.create(cve_id="CVE2304-2333", base_severity="High")
Report.objects.create(dependency=dependency, cve_object=cve, status=Status.REVIEW.name)
self.assertEqual(self.project.vulnerabilities_count([Status.REVIEW.name]), {"High": 1})
14 changes: 14 additions & 0 deletions backend/analyzer/test/test_notification_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,17 @@ def test_mail_sent(self, db):
notification_manager.notify(self.project)
assert len(mail.outbox) == 1
assert mail.outbox[0].recipients()[0] == self.user.username

def test_notify_multiple_users(self, db):
self.user3 = User.objects.create(username="test3@acme.de", notification_threshold=Threshold.HIGH.name)
UserWatchProject.objects.create(project=self.project, user=self.user3)
notification_manager.notify(self.project)
assert len(mail.outbox) == 2
assert mail.outbox[0].recipients()[0] == self.user.username
assert mail.outbox[1].recipients()[0] == self.user3.username

def test_notify_no_users(self, db):
self.user.notification_threshold = Threshold.CRITICAL.name
self.user.save()
notification_manager.notify(self.project)
assert len(mail.outbox) == 0
10 changes: 10 additions & 0 deletions backend/analyzer/test/test_parser/test_owasp.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,13 @@ class JSONTest(TestCase):
def test_valid_json(self):
json_string = get_owasp_json()
self.assertEqual(TRUE_DATA, owasp_parser.parse_json(json_string))

def test_invalid_json(self):
invalid_json_string = '{"invalid": "json"}'
with self.assertRaises(KeyError):
owasp_parser.parse_json(invalid_json_string)

def test_empty_json(self):
empty_json_string = '{}'
with self.assertRaises(KeyError):
owasp_parser.parse_json(empty_json_string)
5 changes: 5 additions & 0 deletions backend/analyzer/test/test_parser_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,8 @@ def test_invalid_types():
def test_invalid_data_calls_message():
with pytest.raises(ParseError):
ParserManager(tool_name="owasp", file_type="json").parse("")


def test_parse_empty_body():
with pytest.raises(ParseError):
ParserManager(tool_name="owasp", file_type="json").parse("")
32 changes: 32 additions & 0 deletions backend/analyzer/test/test_project.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,35 @@ def test_count_vulnerabilities(self, db):
BaseSeverity.NA.name: 1
}
assert true_data == counted

def test_dependency_count(self, db):
assert self.project.dependency_count == 2

def test_resolved_report_count(self, db):
assert self.project.resolved_report_count == 0

def test_solution_distribution(self, db):
solution_distribution = self.project.solution_distribution
assert solution_distribution == {
"NO_SOLUTION_NEEDED": 0,
"SOLUTION_AVAILABLE": 0,
"SOLUTION_IMPLEMENTED": 0,
"SOLUTION_NOT_AVAILABLE": 0,
"SOLUTION_NOT_NEEDED": 0,
"SOLUTION_PARTIALLY_IMPLEMENTED": 0,
"SOLUTION_UNKNOWN": 0,
}

def test_status_distribution(self, db):
status_distribution = self.project.status_distribution
assert status_distribution == {
"REVIEW": 5,
"NO_THREAT": 0,
"THREAT_FIXED": 0,
"THREAT_NOT_FIXED": 0,
"THREAT_UNKNOWN": 0,
}

def test_calculate_risk_score(self, db):
risk_score = self.project.calculate_risk_score
assert risk_score == 1 - (1 - 0) * (1 - 0) * (1 - 0) * (1 - 0) * (1 - 0)
46 changes: 46 additions & 0 deletions backend/analyzer/test/test_project_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,49 @@ def test_update_project(dummy_project):
"CVE-2019-8331"}

assert (set(dependency_reports2)) == {"CVE-2015-9251", "CVE-2019-11358", "CVE-2020-11022", "CVE-2020-11023"}


@pytest.mark.django_db
def test_generate_key(dummy_project):
project_manager = ProjectManager(dummy_project)
key = project_manager.generate_key()
assert len(key) == 43
assert project_manager.verify_key(key)


@pytest.mark.django_db
def test_verify_key(dummy_project):
project_manager = ProjectManager(dummy_project)
key = project_manager.generate_key()
assert project_manager.verify_key(key)
assert not project_manager.verify_key("invalid_key")


@pytest.mark.django_db
def test_update_project_with_no_changes(dummy_project):
data = ParserManager(tool_name="owasp", file_type="json").parse(get_owasp_json())
project_manager = ProjectManager(dummy_project)
project_manager.update_project(data)
assert not project_manager.has_changed(data)


@pytest.mark.django_db
def test_update_project_with_new_data(dummy_project):
data = ParserManager(tool_name="owasp", file_type="json").parse(get_owasp_json())
project_manager = ProjectManager(dummy_project)
project_manager.update_project(data)
new_data = {
"new_dependency:1.0.0": {
"dependency_name": "new_dependency",
"version": "1.0.0",
"path": "path/to/new_dependency",
"license": "MIT",
"vulnerabilities": ["CVE-2021-12345"],
"package_manager": "npm"
}
}
assert project_manager.has_changed(new_data)
project_manager.update_project(new_data)
assert not project_manager.has_changed(new_data)
dependency_names = project_manager.get().dependency_set.values_list("dependency_name", flat=True)
assert "new_dependency" in dependency_names
20 changes: 20 additions & 0 deletions backend/analyzer/test/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,23 @@ def test_threshold_exception(self, setup):

response = self.view(request)
assert response.status_code == 406

def test_health_endpoint(self, setup):
request = self.client.get("/analyzer/health")
response = self.view(request)
assert response.status_code == 200
assert response.content == b"I'm fine!"

def test_analyze_report_with_no_dependencies(self, setup):
empty_report = "{}"
request = self.client.post(self.correct_url, data=empty_report, content_type=self.content_type,
HTTP_API_KEY=self.key)
response = self.view(request)
assert response.status_code == 400
assert b"No dependencies found" in response.content

def test_analyze_report_with_internal_server_error(self, setup):
request = self.client.post(self.correct_url, data=self.report, content_type=self.content_type,
HTTP_API_KEY=self.key)
with pytest.raises(Exception):
self.view(request)
22 changes: 22 additions & 0 deletions backend/webserver/test/test_authentication_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,25 @@ def test_login(self):

auth.logout(request)
assert isinstance(auth.get_user(request), AnonymousUser)

@patch("securecheckplus.settings.LDAP_HOST", None)
def test_update_group(self):
user = User.objects.create(username="TestUser")
group = Group.objects.create(name="basic")
user.groups.add(group)
update_group(user, [LDAP_BASE_GROUP_DN])
assert user.groups.filter(name="basic").exists()

@patch("securecheckplus.settings.LDAP_HOST", None)
def test_authenticate_with_ldap(self):
with patch("webserver.manager.ldap_adapter.LdapAdapter.authenticate_user", return_value=[LDAP_BASE_GROUP_DN]):
user = auth.authenticate(username="ldap_user", password="ldap_password")
assert user is not None
assert user.username == "ldap_user"
assert user.groups.filter(name="basic").exists()

@patch("securecheckplus.settings.LDAP_HOST", None)
def test_authenticate_with_invalid_ldap(self):
with patch("webserver.manager.ldap_adapter.LdapAdapter.authenticate_user", return_value=None):
user = auth.authenticate(username="invalid_ldap_user", password="invalid_ldap_password")
assert user is None
21 changes: 19 additions & 2 deletions backend/webserver/test/test_authorization_manager.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@

import pytest
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from rest_framework.test import APIRequestFactory
from rest_framework.views import APIView

from webserver.manager.authorization_manager import permission_required
from webserver.manager.authorization_manager import permission_required, IsPut, IsPost, IsGet
from webserver.models import User


Expand Down Expand Up @@ -49,3 +48,21 @@ def test_have_access_after_group_change(self, db):
self.user.groups.add(self.admin_group)
self.user.save()
assert permission_manager.has_permission(permission_manager, request, view=APIView.as_view())

def test_is_put_permission(self, db):
permission_manager = IsPut()
request = APIRequestFactory(enforce_csrf_checks=True).put("/", data="", content_type=self.request_content_type)
request.user = self.user
assert permission_manager.has_permission(request=request, view=APIView.as_view())

def test_is_post_permission(self, db):
permission_manager = IsPost()
request = APIRequestFactory(enforce_csrf_checks=True).post("/", data="", content_type=self.request_content_type)
request.user = self.user
assert permission_manager.has_permission(request=request, view=APIView.as_view())

def test_is_get_permission(self, db):
permission_manager = IsGet()
request = APIRequestFactory(enforce_csrf_checks=True).get("/", content_type=self.request_content_type)
request.user = self.user
assert permission_manager.has_permission(request=request, view=APIView.as_view())
Loading
Loading