Skip to content

Commit 16ecba7

Browse files
author
pranavshukla
committed
HybridAnalysis: switch POST /search/hash to GET and add overview fallback
2 parents 781a8a2 + c2c7096 commit 16ecba7

File tree

488 files changed

+73252
-7310
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

488 files changed

+73252
-7310
lines changed

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ Please delete options that are not relevant.
2626
- [ ] Check if it could make sense to add that analyzer/connector to other [freely available playbooks](https://intelowlproject.github.io/docs/IntelOwl/usage/#list-of-pre-built-playbooks).
2727
- [ ] I have provided the resulting raw JSON of a finished analysis and a screenshot of the results.
2828
- [ ] If the plugin interacts with an external service, I have created an attribute called precisely `url` that contains this information. This is required for Health Checks (HEAD HTTP requests).
29-
- [ ] If the plugin requires mocked testing, `_monkeypatch()` was used in its class to apply the necessary decorators.
30-
- [ ] I have added that raw JSON sample to the `MockUpResponse` of the `_monkeypatch()` method. This serves us to provide a valid sample for testing.
29+
- [ ] If a new analyzer has beed added, I have created a unittest for it in the appropriate dir. I have also mocked all the external calls, so that no real calls are being made while testing.
30+
- [ ] I have added that raw JSON sample to the `get_mocker_response()` method of the unittest class. This serves us to provide a valid sample for testing.
3131
- [ ] I have created the corresponding `DataModel` for the new analyzer following the [documentation](https://intelowlproject.github.io/docs/IntelOwl/contribute/#how-to-create-a-datamodel)
3232
- [ ] I have inserted the copyright banner at the start of the file: ```# This file is a part of IntelOwl https://github.com/intelowlproject/IntelOwl # See the file 'LICENSE' for copying permission.```
3333
- [ ] Please avoid adding new libraries as requirements whenever it is possible. Use new libraries only if strictly needed to solve the issue you are working for. In case of doubt, ask a maintainer permission to use a specific library.

.github/workflows/codeql-analysis.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
6666
# Initializes the CodeQL tools for scanning.
6767
- name: Initialize CodeQL
68-
uses: github/codeql-action/init@v3.28.0
68+
uses: github/codeql-action/init@v4.31.0
6969
with:
7070
languages: python
7171
# Override the default behavior so that the action doesn't attempt
@@ -93,4 +93,4 @@ jobs:
9393
# make release
9494

9595
- name: Perform CodeQL Analysis
96-
uses: github/codeql-action/analyze@v3.28.0
96+
uses: github/codeql-action/analyze@v4.31.0

.github/workflows/pull_request_automation.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,9 @@ jobs:
115115
- name: Run test
116116
run: |
117117
docker exec intelowl_uwsgi coverage run manage.py test --keepdb tests
118+
- name: Run async tests
119+
run: |
120+
docker exec intelowl_uwsgi coverage run manage.py test --keepdb async_tests
118121
119122
frontend-tests:
120123
runs-on: ubuntu-latest

.github/workflows/scorecard.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ jobs:
5959
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
6060
# format to the repository Actions tab.
6161
- name: "Upload artifact"
62-
uses: actions/upload-artifact@65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08 # v4.6.0
62+
uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
6363
with:
6464
name: SARIF file
6565
path: results.sarif
@@ -68,6 +68,6 @@ jobs:
6868

6969
# Upload the results to GitHub's code scanning dashboard.
7070
- name: "Upload to code-scanning"
71-
uses: github/codeql-action/upload-sarif@5b6e617dc0241b2d60c2bccea90c56b67eceb797 # v2.22.11
71+
uses: github/codeql-action/upload-sarif@8d77149e0c9e2199ac9cfc90c9e15116f5c69c48 # v2.22.11
7272
with:
7373
sarif_file: results.sarif

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ You can see the full list of all available analyzers in the [documentation](http
6464

6565
As open source project maintainers, we strongly rely on external support to get the resources and time to work on keeping the project alive, with a constant release of new features, bug fixes and general improvements.
6666

67-
Because of this, we joined [Open Collective](https://opencollective.com/intelowl-project) to obtain non-profit equal level status which allows the organization to receive and manage donations transparently. Please support IntelOwl and all the community by choosing a plan (BRONZE, SILVER, etc).
67+
Because of this, we joined [Open Collective](https://opencollective.com/intelowl-project) to obtain US and EU non-profit equal level status which allows the organization to receive and manage donations transparently and with tax exemption. Please support IntelOwl and all the community by choosing a plan (BRONZE, SILVER, etc).
6868

6969
<a href="https://opencollective.com/intelowl-project/donate" target="_blank">
7070
<img src="https://opencollective.com/intelowl-project/donate/[email protected]?color=blue" width=200 />
@@ -78,7 +78,7 @@ Because of this, we joined [Open Collective](https://opencollective.com/intelowl
7878

7979
[Certego](https://certego.net/?utm_source=intelowl) is a MDR (Managed Detection and Response) and Threat Intelligence Provider based in Italy.
8080

81-
IntelOwl was born out of Certego's Threat intelligence R&D division and is constantly maintained and updated thanks to them.
81+
IntelOwl was born out of Certego's Threat intelligence R&D division and is mostly maintained and updated thanks to them.
8282

8383
#### The Honeynet Project
8484

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import rest_framework_filters as filters
2+
from django_filters.widgets import QueryArrayWidget
3+
4+
from api_app.analyzables_manager.models import Analyzable
5+
6+
7+
class CharInFilter(filters.BaseInFilter, filters.CharFilter):
8+
pass
9+
10+
11+
class AnalyzableFilter(filters.FilterSet):
12+
name = CharInFilter(widget=QueryArrayWidget)
13+
14+
class Meta:
15+
model = Analyzable
16+
fields = {
17+
"discovery_date": ["lte", "gte"],
18+
}

api_app/analyzables_manager/models.py

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import logging
12
from typing import Type, Union
23

34
from django.core.exceptions import ValidationError
@@ -7,17 +8,14 @@
78

89
from api_app.analyzables_manager.queryset import AnalyzableQuerySet
910
from api_app.choices import Classification
10-
from api_app.data_model_manager.models import (
11-
BaseDataModel,
12-
DomainDataModel,
13-
FileDataModel,
14-
IPDataModel,
15-
)
11+
from api_app.data_model_manager.models import BaseDataModel
1612
from api_app.data_model_manager.queryset import BaseDataModelQuerySet
1713
from api_app.defaults import file_directory_path
1814
from api_app.helpers import calculate_md5, calculate_sha1, calculate_sha256
1915
from certego_saas.models import User
2016

17+
logger = logging.getLogger(__name__)
18+
2119

2220
class Analyzable(models.Model):
2321
name = models.CharField(max_length=255)
@@ -82,23 +80,11 @@ def get_all_user_events_data_model(
8280
).values_list("data_model__pk", flat=True)
8381
)
8482
query |= query2
83+
logger.debug(f"{query=}")
8584
return self.get_data_model_class().objects.filter(query)
8685

8786
def get_data_model_class(self) -> Type[BaseDataModel]:
88-
if self.classification == Classification.IP.value:
89-
return IPDataModel
90-
elif self.classification in [
91-
Classification.URL.value,
92-
Classification.DOMAIN.value,
93-
]:
94-
return DomainDataModel
95-
elif self.classification in [
96-
Classification.HASH.value,
97-
Classification.FILE.value,
98-
]:
99-
return FileDataModel
100-
else:
101-
raise NotImplementedError()
87+
return self.CLASSIFICATIONS.get_data_model_class(self.classification)
10288

10389
def _set_hashes(self, value: Union[str, bytes]):
10490
if isinstance(value, str):
@@ -111,6 +97,10 @@ def _set_hashes(self, value: Union[str, bytes]):
11197
self.sha1 = calculate_sha1(value)
11298

11399
def clean(self):
100+
if self.file:
101+
self.classification = Classification.FILE.value
102+
else:
103+
self.classification = Classification.calculate_observable(self.name)
114104
if self.classification == Classification.FILE.value:
115105
from api_app.analyzers_manager.models import MimeTypes
116106

api_app/analyzables_manager/queryset.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,21 @@ class AnalyzableQuerySet(QuerySet):
1010
def visible_for_user(self, user):
1111

1212
from api_app.models import Job
13+
from api_app.user_events_manager.models import UserAnalyzableEvent
1314

14-
analyzables = (
15+
analyzables_job = (
1516
Job.objects.visible_for_user(user)
1617
.values("analyzable")
1718
.distinct()
1819
.values_list("analyzable__pk", flat=True)
1920
)
20-
return self.filter(pk__in=analyzables)
21+
analyzables_ue = (
22+
UserAnalyzableEvent.objects.visible_for_user(user)
23+
.values("analyzable")
24+
.distinct()
25+
.values_list("analyzable__pk", flat=True)
26+
)
27+
return self.filter(pk__in=analyzables_job) | self.filter(pk__in=analyzables_ue)
2128

2229
def create(self, *args, **kwargs):
2330
obj = self.model(**kwargs)
Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,64 @@
1-
from rest_framework.serializers import ModelSerializer
1+
import logging
2+
3+
from rest_framework import serializers as rfs
24

35
from api_app.analyzables_manager.models import Analyzable
6+
from api_app.choices import Classification
7+
from api_app.models import Job
48
from api_app.serializers.job import JobRelatedField
59

10+
logger = logging.getLogger(__name__)
11+
612

7-
class AnalyzableSerializer(ModelSerializer):
13+
class AnalyzableSerializer(rfs.ModelSerializer):
814
jobs = JobRelatedField(many=True, read_only=True)
915

1016
class Meta:
1117
model = Analyzable
1218
fields = "__all__"
19+
read_only_fields = [
20+
"jobs",
21+
"discovery_date",
22+
"md5",
23+
"classification",
24+
"sha256",
25+
"sha1",
26+
"mimetype",
27+
]
28+
29+
def to_representation(self, instance):
30+
logger.debug(f"{instance=}")
31+
analyzable = super().to_representation(instance)
32+
job = (
33+
Job.objects.filter(id__in=analyzable["jobs"])
34+
.order_by("-finished_analysis_time")
35+
.first()
36+
)
37+
user_event_data_model = (
38+
instance.get_all_user_events_data_model().order_by("-date").first()
39+
)
40+
if not job and not user_event_data_model:
41+
analyzable["last_data_model"] = None
42+
return analyzable
43+
elif (job and job.data_model) or user_event_data_model:
44+
if not job or not job.data_model:
45+
last_data_model = user_event_data_model
46+
elif not user_event_data_model:
47+
last_data_model = job.data_model
48+
else:
49+
if (job and job.data_model) and (
50+
job.data_model.date > user_event_data_model.date
51+
):
52+
last_data_model = job.data_model
53+
else:
54+
last_data_model = user_event_data_model
55+
56+
serializer_class = Classification.get_data_model_class(
57+
classification=analyzable["classification"],
58+
).get_serializer()
59+
analyzable["last_data_model"] = serializer_class(last_data_model).data
60+
return analyzable
61+
62+
def create(self, validated_data):
63+
instance, _ = self.Meta.model.objects.get_or_create(**validated_data)
64+
return instance
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,76 @@
1+
import logging
2+
from http import HTTPStatus
3+
14
from rest_framework import viewsets
5+
from rest_framework.decorators import action
6+
from rest_framework.exceptions import ValidationError
27
from rest_framework.permissions import IsAuthenticated
8+
from rest_framework.response import Response
39

10+
from api_app.analyzables_manager.filters import AnalyzableFilter
11+
from api_app.analyzables_manager.models import Analyzable
412
from api_app.analyzables_manager.serializers import AnalyzableSerializer
13+
from api_app.serializers.job import JobAnalyzableHistorySerializer
14+
from api_app.user_events_manager.serializers import (
15+
UserAnalyzableEventSerializer,
16+
UserDomainWildCardEventSerializer,
17+
UserIPWildCardEventSerializer,
18+
)
19+
20+
logger = logging.getLogger(__name__)
521

622

723
class AnalyzableViewSet(viewsets.ReadOnlyModelViewSet):
824

925
serializer_class = AnalyzableSerializer
1026
permission_classes = [IsAuthenticated]
27+
queryset = Analyzable.objects.all()
28+
filterset_class = AnalyzableFilter
1129

1230
def get_queryset(self):
1331
user = self.request.user
1432
return super().get_queryset().visible_for_user(user)
33+
34+
@action(detail=True)
35+
def history(self, request, pk=None):
36+
user = request.user
37+
try:
38+
analyzable: Analyzable = self.get_queryset().get(pk=pk)
39+
except Analyzable.DoesNotExist:
40+
raise ValidationError({"detail": "Requested analyzable does not exist."})
41+
42+
jobs_queryset = analyzable.jobs.visible_for_user(user).order_by(
43+
"-finished_analysis_time"
44+
)
45+
user_events_queryset = analyzable.user_events.visible_for_user(user).order_by(
46+
"-date"
47+
)
48+
user_domain_wildcard_events_queryset = (
49+
analyzable.user_domain_wildcard_events.visible_for_user(user).order_by(
50+
"-date"
51+
)
52+
)
53+
user_ip_wildcard_events_queryset = (
54+
analyzable.user_ip_wildcard_events.visible_for_user(user).order_by("-date")
55+
)
56+
57+
jobs = JobAnalyzableHistorySerializer(jobs_queryset, many=True).data
58+
user_events = UserAnalyzableEventSerializer(
59+
user_events_queryset, many=True
60+
).data
61+
user_domain_wildcard_events = UserDomainWildCardEventSerializer(
62+
user_domain_wildcard_events_queryset, many=True
63+
).data
64+
user_ip_wildcard_events = UserIPWildCardEventSerializer(
65+
user_ip_wildcard_events_queryset, many=True
66+
).data
67+
68+
return Response(
69+
status=HTTPStatus.OK.value,
70+
data={
71+
"jobs": jobs,
72+
"user_events": user_events,
73+
"user_domain_wildcard_events": user_domain_wildcard_events,
74+
"user_ip_wildcard_events": user_ip_wildcard_events,
75+
},
76+
)

0 commit comments

Comments
 (0)