Skip to content

case_handler models

case_handler.models

Status

Bases: TextChoices

Enumeration of possible case statuses.

TODO = ('To Do', _('To Do')) class-attribute instance-attribute

ONGOING = ('On Going', _('On Going')) class-attribute instance-attribute

CHALLENGED = ('Challenged', _('Challenged')) class-attribute instance-attribute

DONE = ('Done', _('Done')) class-attribute instance-attribute

Result

Bases: TextChoices

Enumeration of possible case result statuses.

SAFE = ('Safe', _('Safe')) class-attribute instance-attribute

INCONCLUSIVE = ('Inconclusive', _('Inconclusive')) class-attribute instance-attribute

UNCHALLENGED = ('Unchallenged', _('Unchallenged')) class-attribute instance-attribute

ALLOW_LISTED = ('AllowListed', _('AllowListed')) class-attribute instance-attribute

FAILURE = ('Failure', _('Failure')) class-attribute instance-attribute

SUSPICIOUS = ('Suspicious', _('Suspicious')) class-attribute instance-attribute

DANGEROUS = ('Dangerous', _('Dangerous')) class-attribute instance-attribute

ProposedVerdict

Bases: TextChoices

SAFE = ('Safe', _('Safe')) class-attribute instance-attribute

DANGEROUS = ('Dangerous', _('Dangerous')) class-attribute instance-attribute

Case

Bases: Model

Main incident investigation case, storing scores, analyst decisions and AI predictions.

description = models.TextField() class-attribute instance-attribute

reporter = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='cases') class-attribute instance-attribute

analysis_done = models.PositiveIntegerField(default=0) class-attribute instance-attribute

status = models.CharField(max_length=20, choices=Status.choices, default=Status.TODO, verbose_name='Status', db_index=True) class-attribute instance-attribute

results = models.CharField(max_length=20, choices=Result.choices, default=Result.INCONCLUSIVE, verbose_name='Results', db_index=True) class-attribute instance-attribute

final_score = models.FloatField(default=0) class-attribute instance-attribute

final_confidence = models.FloatField(default=0) class-attribute instance-attribute

score = models.FloatField(default=0) class-attribute instance-attribute

confidence = models.FloatField(default=0) class-attribute instance-attribute

results_ai = models.CharField(max_length=20, default='Suspicious', verbose_name='ResultsAI') class-attribute instance-attribute

score_ai = models.FloatField(default=0) class-attribute instance-attribute

confidence_ai = models.FloatField(default=0) class-attribute instance-attribute

category_ai = models.CharField(max_length=20, default='Uncategorized', verbose_name='Category AI', db_index=True) class-attribute instance-attribute

fileOrMail = models.ForeignKey('CaseHasFileOrMail', on_delete=models.CASCADE, related_name='cases', null=True, blank=True) class-attribute instance-attribute

nonFileIocs = models.ForeignKey('CaseHasNonFileIocs', on_delete=models.CASCADE, related_name='cases', null=True, blank=True) class-attribute instance-attribute

observable_group = models.ForeignKey('ObservableGroup', on_delete=models.CASCADE, related_name='cases', null=True, blank=True) class-attribute instance-attribute

is_challenged = models.BooleanField(default=False) class-attribute instance-attribute

is_challengeable = models.BooleanField(default=True) class-attribute instance-attribute

challenged_result = models.CharField(max_length=20, choices=Result.choices, default=Result.UNCHALLENGED, verbose_name='Challenged Result') class-attribute instance-attribute

creation_date = models.DateTimeField(auto_now_add=True, db_index=True) class-attribute instance-attribute

last_update = models.DateTimeField(auto_now=True) class-attribute instance-attribute

last_update_by = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='cases_last_update_by', null=True, blank=True) class-attribute instance-attribute

lifecycle_state = models.CharField(max_length=20, choices=LifecycleState.choices, default=LifecycleState.CREATED, db_index=True, verbose_name='Lifecycle State') class-attribute instance-attribute

finalized_at = models.DateTimeField(null=True, blank=True, verbose_name='Finalized At') class-attribute instance-attribute

dispatched_at = models.DateTimeField(null=True, blank=True, verbose_name='Dispatched At') class-attribute instance-attribute

kpi_counted = models.BooleanField(default=False, verbose_name='KPI Counted') class-attribute instance-attribute

challenge_proposed_result = models.CharField(max_length=20, choices=ProposedVerdict.choices, blank=True, default='', verbose_name='Challenge Proposed Result') class-attribute instance-attribute

challenge_reason = models.TextField(blank=True, default='', verbose_name='Challenge Reason') class-attribute instance-attribute

reporter_context = models.TextField(blank=True, default='', verbose_name='Reporter Context') class-attribute instance-attribute

thehive_alert_id = models.CharField(max_length=64, blank=True, default='', verbose_name='TheHive Alert ID') class-attribute instance-attribute

external_ref = models.CharField(max_length=255, blank=True, default='', db_index=True, verbose_name='External Reference') class-attribute instance-attribute

is_allowlisted = models.BooleanField(default=False, verbose_name='Is Allowlisted') class-attribute instance-attribute

is_denylisted = models.BooleanField(default=False, verbose_name='Is Denylisted') class-attribute instance-attribute

list_reason = models.TextField(blank=True, default='', verbose_name='Allow/Deny List Reason') class-attribute instance-attribute

inconclusive_reason = models.CharField(max_length=20, blank=True, default='') class-attribute instance-attribute

verdict_rationale = models.JSONField(default=list, blank=True) class-attribute instance-attribute

verdict_explanation = models.JSONField(null=True, blank=True, default=None) class-attribute instance-attribute

Meta

ordering = ['-creation_date'] class-attribute instance-attribute
constraints = [models.CheckConstraint(condition=models.Q(observable_group__isnull=True) | models.Q(fileOrMail__isnull=True) & models.Q(nonFileIocs__isnull=True), name='case_group_excludes_other_roads_chk')] class-attribute instance-attribute
indexes = [models.Index(fields=['status', '-creation_date'], name='case_status_created_idx')] class-attribute instance-attribute

was_published_recently()

Check if the case was created in the last 24 hours.

CaseChallengeToken

Bases: Model

case = models.ForeignKey(Case, on_delete=models.CASCADE, related_name='challenge_tokens') class-attribute instance-attribute

token_hash = models.CharField(max_length=64, unique=True, db_index=True) class-attribute instance-attribute

expires_at = models.DateTimeField(db_index=True) class-attribute instance-attribute

used_at = models.DateTimeField(null=True, blank=True, db_index=True) class-attribute instance-attribute

created_at = models.DateTimeField(auto_now_add=True) class-attribute instance-attribute

Meta

indexes = [models.Index(fields=['case', 'expires_at'], name='cct_case_expires_idx'), models.Index(fields=['case', 'used_at'], name='cct_case_used_idx')] class-attribute instance-attribute

hash_token(token) staticmethod

issue_token(case, *, lifetime=None) classmethod

build_challenge_url(case_id, token, api_base) staticmethod

normalize_api_base(submissions_url) staticmethod

mark_used()

CaseComment

Bases: Model

case = models.ForeignKey(Case, on_delete=models.CASCADE, related_name='comments') class-attribute instance-attribute

author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='case_comments') class-attribute instance-attribute

body = models.TextField() class-attribute instance-attribute

is_internal = models.BooleanField(default=False, verbose_name='Internal (analyst-only)') class-attribute instance-attribute

created_at = models.DateTimeField(auto_now_add=True, db_index=True) class-attribute instance-attribute

Meta

ordering = ['-created_at'] class-attribute instance-attribute

CaseHasFileOrMail

Bases: Model

Association model for cases linked to either a file or an email (one-to-one per instance).

case = models.ForeignKey('Case', on_delete=models.CASCADE, related_name='case_has_file_or_mail') class-attribute instance-attribute

file = models.ForeignKey(File, on_delete=models.CASCADE, related_name='case_has_file_or_mail', null=True, blank=True) class-attribute instance-attribute

mail = models.ForeignKey(Mail, on_delete=models.CASCADE, related_name='case_has_file_or_mail', null=True, blank=True) class-attribute instance-attribute

creation_date = models.DateTimeField(auto_now_add=True, db_index=True) class-attribute instance-attribute

last_update = models.DateTimeField(auto_now=True) class-attribute instance-attribute

Meta

ordering = ['-creation_date'] class-attribute instance-attribute
constraints = [models.CheckConstraint(condition=exactly_one_not_null('file', 'mail'), name='casehasfileormail_one_target_chk')] class-attribute instance-attribute

get_iocs()

Return the associated file or mail object in a dict.

CaseHasNonFileIocs

Bases: Model

Association model for cases linked to non-file IOCs: URLs, IPs, and hashes.

case = models.ForeignKey('Case', on_delete=models.CASCADE, related_name='case_has_non_file_iocs') class-attribute instance-attribute

url = models.ForeignKey(URL, on_delete=models.CASCADE, related_name='case_has_non_file_iocs', null=True, blank=True) class-attribute instance-attribute

ip = models.ForeignKey(IP, on_delete=models.CASCADE, related_name='case_has_non_file_iocs', null=True, blank=True) class-attribute instance-attribute

hash = models.ForeignKey(Hash, on_delete=models.CASCADE, related_name='case_has_non_file_iocs', null=True, blank=True) class-attribute instance-attribute

creation_date = models.DateTimeField(auto_now_add=True, db_index=True) class-attribute instance-attribute

last_update = models.DateTimeField(auto_now=True) class-attribute instance-attribute

Meta

ordering = ['-creation_date'] class-attribute instance-attribute
constraints = [models.CheckConstraint(condition=exactly_one_not_null('url', 'ip', 'hash'), name='casehasnonfileiocs_one_target_chk')] class-attribute instance-attribute

get_iocs()

Return the associated URL, IP, or hash as a dict.

CaseArtifact

Bases: Model

Per-(case, artifact) link replacing the old FileInCases / HashInCases / UrlInCases / IpInCases / MailInCases models. Exactly one of file, hash, url, ip, mail is populated per row; artifact_type matches the populated FK.

case = models.ForeignKey('Case', on_delete=models.CASCADE, related_name='case_artifacts') class-attribute instance-attribute

artifact_type = models.CharField(max_length=10, choices=ArtifactType.choices, db_index=True) class-attribute instance-attribute

file = models.ForeignKey(File, on_delete=models.CASCADE, related_name='case_artifacts', null=True, blank=True) class-attribute instance-attribute

hash = models.ForeignKey(Hash, on_delete=models.CASCADE, related_name='case_artifacts', null=True, blank=True) class-attribute instance-attribute

url = models.ForeignKey(URL, on_delete=models.CASCADE, related_name='case_artifacts', null=True, blank=True) class-attribute instance-attribute

ip = models.ForeignKey(IP, on_delete=models.CASCADE, related_name='case_artifacts', null=True, blank=True) class-attribute instance-attribute

mail = models.ForeignKey(Mail, on_delete=models.CASCADE, related_name='case_artifacts', null=True, blank=True) class-attribute instance-attribute

creation_date = models.DateTimeField(auto_now_add=True, db_index=True) class-attribute instance-attribute

last_update = models.DateTimeField(auto_now=True) class-attribute instance-attribute

ArtifactType

Bases: TextChoices

FILE = ('file', 'File') class-attribute instance-attribute
HASH = ('hash', 'Hash') class-attribute instance-attribute
URL = ('url', 'URL') class-attribute instance-attribute
IP = ('ip', 'IP') class-attribute instance-attribute
MAIL = ('mail', 'Mail') class-attribute instance-attribute

Meta

ordering = ['-creation_date'] class-attribute instance-attribute
constraints = [models.CheckConstraint(condition=exactly_one_not_null('file', 'hash', 'url', 'ip', 'mail'), name='caseartifact_one_target_chk')] class-attribute instance-attribute

ObservableGroup

Bases: Model

A set of indicators submitted together and analysed as one Case. The IOC-road analogue of Mail: a thin envelope over N observables.

label = models.CharField(max_length=255, blank=True, default='') class-attribute instance-attribute

creation_date = models.DateTimeField(auto_now_add=True, db_index=True) class-attribute instance-attribute

last_update = models.DateTimeField(auto_now=True) class-attribute instance-attribute

Meta

ordering = ['-creation_date'] class-attribute instance-attribute

ObservableGroupArtifact

Bases: Model

group = models.ForeignKey(ObservableGroup, on_delete=models.CASCADE, related_name='artifacts') class-attribute instance-attribute

artifact_type = models.CharField(max_length=10, choices=Type.choices, db_index=True) class-attribute instance-attribute

url = models.ForeignKey(URL, on_delete=models.CASCADE, null=True, blank=True, related_name='observable_group_artifacts') class-attribute instance-attribute

ip = models.ForeignKey(IP, on_delete=models.CASCADE, null=True, blank=True, related_name='observable_group_artifacts') class-attribute instance-attribute

hash = models.ForeignKey(Hash, on_delete=models.CASCADE, null=True, blank=True, related_name='observable_group_artifacts') class-attribute instance-attribute

domain = models.ForeignKey(Domain, on_delete=models.CASCADE, null=True, blank=True, related_name='observable_group_artifacts') class-attribute instance-attribute

creation_date = models.DateTimeField(auto_now_add=True, db_index=True) class-attribute instance-attribute

Type

Bases: TextChoices

URL = ('URL', 'URL') class-attribute instance-attribute
IP = ('IP', 'IP') class-attribute instance-attribute
HASH = ('HASH', 'Hash') class-attribute instance-attribute
DOMAIN = ('DOMAIN', 'Domain') class-attribute instance-attribute

Meta

ordering = ['creation_date'] class-attribute instance-attribute
constraints = [models.CheckConstraint(condition=exactly_one_not_null('url', 'ip', 'hash', 'domain'), name='observablegroupartifact_one_target_chk')] class-attribute instance-attribute
indexes = [models.Index(fields=['group', 'artifact_type'])] class-attribute instance-attribute

observable()

Campaign

Bases: Model

A group of similar dangerous mails, detected at finalisation.

ref is the stable identity everything else keys on (the Campaigns page groups ChromaDB documents by it, and it is the TheHive alert's sourceRef). external_refs maps a connector name to its object id, e.g. {"thehive": "~123"}.

ref = models.CharField(max_length=32, unique=True, default=_new_campaign_ref) class-attribute instance-attribute

title = models.CharField(max_length=255, blank=True) class-attribute instance-attribute

created_at = models.DateTimeField(auto_now_add=True) class-attribute instance-attribute

updated_at = models.DateTimeField(auto_now=True) class-attribute instance-attribute

external_refs = models.JSONField(default=dict, blank=True) class-attribute instance-attribute

CampaignMember

Bases: Model

A case in a campaign; synced lists the connectors that already got it.

campaign = models.ForeignKey(Campaign, on_delete=models.CASCADE, related_name='members') class-attribute instance-attribute

case = models.OneToOneField(Case, on_delete=models.CASCADE, related_name='campaign_membership') class-attribute instance-attribute

joined_at = models.DateTimeField(auto_now_add=True) class-attribute instance-attribute

synced = models.JSONField(default=list, blank=True) class-attribute instance-attribute

Meta

ordering = ['joined_at', 'id'] class-attribute instance-attribute