Skip to content

Commit 0c362ba

Browse files
committed
add: transform models to MISP format
1 parent ea7c639 commit 0c362ba

8 files changed

Lines changed: 121 additions & 12 deletions

File tree

api/app/models/attribute.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import uuid
22

3+
import logging
34
from app.database import Base
45
from app.models.event import DistributionLevel
56
from sqlalchemy import BigInteger, Boolean, Column, Enum, ForeignKey, Integer, String
67
from sqlalchemy.dialects.postgresql import UUID
78
from sqlalchemy.orm import Mapped, mapped_column, relationship
9+
from app.services.minio import get_minio_client
10+
from app.settings import Settings, get_settings
11+
12+
13+
logger = logging.getLogger(__name__)
814

915

1016
class Attribute(Base):
@@ -37,3 +43,45 @@ class Attribute(Base):
3743
last_seen = Column(BigInteger(), index=True)
3844

3945
tags = relationship("Tag", secondary="attribute_tags", lazy="subquery")
46+
47+
def to_misp_format(
48+
self,
49+
settings: Settings = get_settings(),
50+
):
51+
"""Convert the Attribute to a MISP-compatible dictionary representation."""
52+
53+
attr_json = {
54+
"id": self.id,
55+
"event_id": self.event_id,
56+
"object_id": self.object_id,
57+
"object_relation": self.object_relation,
58+
"category": self.category,
59+
"type": self.type,
60+
"value": self.value,
61+
"to_ids": self.to_ids,
62+
"uuid": str(self.uuid),
63+
"timestamp": self.timestamp,
64+
"distribution": self.distribution.name if self.distribution else None,
65+
"sharing_group_id": self.sharing_group_id,
66+
"comment": self.comment,
67+
"deleted": self.deleted,
68+
"disable_correlation": self.disable_correlation,
69+
"first_seen": self.first_seen,
70+
"last_seen": self.last_seen,
71+
"Tags": [tag.to_misp_format() for tag in self.tags],
72+
}
73+
74+
# if its a file attribute, we need to handle it differently
75+
if self.type in ["malware-sample", "attachment"]:
76+
try:
77+
MinioClient = get_minio_client()
78+
data = MinioClient.get_object(settings.Storage.minio.bucket, self.uuid)
79+
file_content = data.read()
80+
file_b64 = file_content.encode("base64")
81+
attr_json["data"] = file_b64
82+
except Exception as e:
83+
file_b64 = None
84+
logger.error(f"Error storing attachment: {str(e)}")
85+
print(f"Error fetching file from storage: {str(e)}")
86+
87+
return attr_json

api/app/models/event.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ class Event(Base):
9292
tags = relationship("Tag", secondary="event_tags", lazy="joined")
9393
organisation = relationship("Organisation", lazy="joined", uselist=False, foreign_keys=[org_id])
9494

95-
def to_misp_event(self):
95+
def to_misp_format(self):
96+
"""Convert the Event to a MISP-compatible dictionary representation."""
97+
9698
return {
9799
"id": self.id,
98100
"org_id": self.org_id,
@@ -114,11 +116,12 @@ def to_misp_event(self):
114116
"extends_uuid": str(self.extends_uuid) if self.extends_uuid else None,
115117
"protected": self.protected,
116118
"deleted": self.deleted,
117-
# "Attributes": [attribute.to_misp_attribute() for attribute in self.attributes],
118-
# "Objects": [obj.to_misp_object() for obj in self.objects],
119-
# "Tags": [tag.name for tag in self.tags],
119+
"Attributes": [attribute.to_misp_format() for attribute in self.attributes],
120+
"Objects": [obj.to_misp_format() for obj in self.objects],
121+
"Tags": [tag.to_misp_format() for tag in self.tags],
120122
"Organisation": {
121123
"id": self.organisation.id,
122-
"name": self.organisation.name
124+
"name": self.organisation.name,
125+
"uuid": str(self.organisation.uuid),
123126
} if self.organisation else None
124127
}

api/app/models/object.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,25 @@ class Object(Base):
3232

3333
attributes = relationship("Attribute", lazy="subquery", cascade="all, delete-orphan")
3434
object_references = relationship("ObjectReference", lazy="subquery", cascade="all, delete-orphan")
35+
36+
def to_misp_format(self):
37+
"""Convert the Object to a MISP-compatible dictionary representation."""
38+
return {
39+
"id": self.id,
40+
"name": self.name,
41+
"meta_category": self.meta_category,
42+
"description": self.description,
43+
"template_uuid": self.template_uuid,
44+
"template_version": self.template_version,
45+
"event_id": self.event_id,
46+
"uuid": str(self.uuid),
47+
"timestamp": self.timestamp,
48+
"distribution": self.distribution.name if self.distribution else None,
49+
"sharing_group_id": self.sharing_group_id,
50+
"comment": self.comment,
51+
"deleted": self.deleted,
52+
"first_seen": self.first_seen,
53+
"last_seen": self.last_seen,
54+
"Attributes": [attribute.to_misp_format() for attribute in self.attributes],
55+
"ObjectReference": [ref.to_misp_format() for ref in self.object_references],
56+
}

api/app/models/object_reference.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,3 +33,20 @@ class ObjectReference(Base):
3333
relationship_type = Column(String)
3434
comment = Column(String, nullable=False)
3535
deleted = Column(Boolean, nullable=False, default=False)
36+
37+
def to_misp_format(self):
38+
"""Convert the ObjectReference to a MISP-compatible dictionary representation."""
39+
return {
40+
"id": self.id,
41+
"uuid": str(self.uuid),
42+
"timestamp": self.timestamp,
43+
"object_id": self.object_id,
44+
"event_id": self.event_id,
45+
"source_uuid": str(self.source_uuid) if self.source_uuid else None,
46+
"referenced_uuid": str(self.referenced_uuid) if self.referenced_uuid else None,
47+
"referenced_id": self.referenced_id,
48+
"referenced_type": self.referenced_type.name if self.referenced_type else None,
49+
"relationship_type": self.relationship_type,
50+
"comment": self.comment,
51+
"deleted": self.deleted,
52+
}

api/app/models/tag.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ class Tag(Base):
1818
is_custom_galaxy = Column(Boolean, nullable=False, default=False)
1919
local_only = Column(Boolean, nullable=False, default=False)
2020

21+
def to_misp_format(self):
22+
"""Convert the Tag to a MISP-compatible dictionary representation."""
23+
return {
24+
"id": self.id,
25+
"name": self.name,
26+
"colour": self.colour,
27+
"exportable": self.exportable,
28+
"org_id": self.org_id,
29+
"user_id": self.user_id,
30+
"hide_tag": self.hide_tag,
31+
"numerical_value": self.numerical_value,
32+
"is_galaxy": self.is_galaxy,
33+
"is_custom_galaxy": self.is_custom_galaxy,
34+
"local_only": self.local_only,
35+
}
36+
2137

2238
class EventTag(Base):
2339
__tablename__ = "event_tags"

api/app/repositories/servers.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -723,7 +723,7 @@ def push_event_by_uuid(
723723

724724
try:
725725
response = remote_misp._prepare_request(
726-
"POST", f"events/add/{event_uuid}", data=db_event.to_misp_event()
726+
"POST", f"events/add/{event_uuid}", data=db_event.to_misp_format()
727727
)
728728
if response.status_code == 200:
729729
return True

api/app/routers/events.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,16 +198,20 @@ def untag_event(
198198
status_code=status.HTTP_200_OK,
199199
)
200200
async def upload_attachments(
201-
event_id: int,
201+
event_id: Union[int, UUID],
202202
attachments: list[UploadFile],
203203
attachments_meta: Annotated[str, Form()],
204204
db: Session = Depends(get_db),
205205
user: user_schemas.User = Security(
206206
get_current_active_user, scopes=["events:update"]
207207
),
208208
) -> list[object_schemas.Object]:
209-
event = events_repository.get_event_by_id(db, event_id=event_id)
210-
if event is None:
209+
if isinstance(event_id, int):
210+
db_event = events_repository.get_event_by_id(db, event_id=event_id)
211+
else:
212+
db_event = events_repository.get_event_by_uuid(db, event_uuid=event_id)
213+
214+
if db_event is None:
211215
raise HTTPException(
212216
status_code=status.HTTP_404_NOT_FOUND, detail="Event not found"
213217
)
@@ -216,9 +220,9 @@ async def upload_attachments(
216220
attachments_meta = json.loads(attachments_meta)
217221

218222
objects = attachments_repository.upload_attachments_to_event(
219-
db=db, event=event, attachments=attachments, attachments_meta=attachments_meta
223+
db=db, event=db_event, attachments=attachments, attachments_meta=attachments_meta
220224
)
221-
tasks.index_event.delay(event.uuid)
225+
tasks.index_event.delay(db_event.uuid)
222226

223227
return objects
224228

frontend/src/views/Login.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,6 @@ function onSubmit(values, { setErrors }) {
9595
<button class="w-100 btn btn-lg btn-outline-primary" type="submit">
9696
Sign in
9797
</button>
98-
<p class="mt-3 mb-3 text-muted">&copy; 2024</p>
9998
</Form>
10099
</div>
101100
</template>

0 commit comments

Comments
 (0)