case_handler models¶
case_handler.models
¶
Status
¶
Bases: TextChoices
Enumeration of possible case statuses.
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
¶
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
¶
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
¶
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
¶
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
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.
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
¶
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.