412 lines
13 KiB
Python
412 lines
13 KiB
Python
"""
|
|
Scam and fraud report models.
|
|
"""
|
|
from django.db import models
|
|
from django.contrib.auth import get_user_model
|
|
from django.urls import reverse
|
|
from django.utils.text import slugify
|
|
from django.core.cache import cache
|
|
from accounts.security import DataEncryption
|
|
|
|
User = get_user_model()
|
|
|
|
|
|
class SiteSettings(models.Model):
|
|
"""
|
|
Site-wide settings that can be managed from admin.
|
|
Uses singleton pattern - only one instance should exist.
|
|
"""
|
|
contact_email = models.EmailField(
|
|
default='support@fraudplatform.bg',
|
|
help_text='Основен имейл за контакти и поддръжка'
|
|
)
|
|
contact_phone = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
default='',
|
|
help_text='Телефонен номер за контакти (незадължително)'
|
|
)
|
|
contact_address = models.CharField(
|
|
max_length=200,
|
|
blank=True,
|
|
default='',
|
|
help_text='Адрес за контакти (незадължително)'
|
|
)
|
|
|
|
# Email Server Settings
|
|
email_backend = models.CharField(
|
|
max_length=100,
|
|
default='django.core.mail.backends.smtp.EmailBackend',
|
|
choices=[
|
|
('django.core.mail.backends.smtp.EmailBackend', 'SMTP'),
|
|
('django.core.mail.backends.console.EmailBackend', 'Console (Development)'),
|
|
('django.core.mail.backends.filebased.EmailBackend', 'File Based'),
|
|
],
|
|
help_text='Тип на имейл сървъра'
|
|
)
|
|
email_host = models.CharField(
|
|
max_length=255,
|
|
blank=True,
|
|
default='',
|
|
help_text='SMTP сървър (напр. smtp.gmail.com)'
|
|
)
|
|
email_port = models.IntegerField(
|
|
default=587,
|
|
help_text='SMTP порт (обикновено 587 за TLS или 465 за SSL)'
|
|
)
|
|
email_use_tls = models.BooleanField(
|
|
default=True,
|
|
help_text='Използване на TLS (за порт 587)'
|
|
)
|
|
email_use_ssl = models.BooleanField(
|
|
default=False,
|
|
help_text='Използване на SSL (за порт 465)'
|
|
)
|
|
email_host_user = models.CharField(
|
|
max_length=255,
|
|
blank=True,
|
|
default='',
|
|
help_text='SMTP потребителско име / имейл'
|
|
)
|
|
email_host_password = models.CharField(
|
|
max_length=255,
|
|
blank=True,
|
|
default='',
|
|
help_text='SMTP парола (ще бъде криптирана)'
|
|
)
|
|
default_from_email = models.EmailField(
|
|
default='noreply@fraudplatform.bg',
|
|
help_text='Имейл адрес по подразбиране за изпращане'
|
|
)
|
|
email_timeout = models.IntegerField(
|
|
default=10,
|
|
help_text='Таймаут за имейл връзка (секунди)'
|
|
)
|
|
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
|
|
class Meta:
|
|
verbose_name = 'Настройки на Сайта'
|
|
verbose_name_plural = 'Настройки на Сайта'
|
|
db_table = 'reports_sitesettings'
|
|
|
|
def __str__(self):
|
|
return 'Настройки на Сайта'
|
|
|
|
def save(self, *args, **kwargs):
|
|
# Ensure only one instance exists
|
|
self.pk = 1
|
|
|
|
# Encrypt email password if it's provided and not already encrypted
|
|
if self.email_host_password:
|
|
# Check if it's already encrypted by trying to decrypt it
|
|
# If decryption succeeds, it's already encrypted, so keep original
|
|
# If decryption fails, it's plain text, so encrypt it
|
|
is_encrypted = False
|
|
try:
|
|
# Try to decrypt - if it succeeds, it's already encrypted
|
|
DataEncryption.decrypt(self.email_host_password)
|
|
is_encrypted = True
|
|
except (Exception, ValueError, TypeError):
|
|
# Decryption failed, so it's plain text
|
|
is_encrypted = False
|
|
|
|
# Only encrypt if it's not already encrypted
|
|
if not is_encrypted:
|
|
self.email_host_password = DataEncryption.encrypt(self.email_host_password)
|
|
|
|
super().save(*args, **kwargs)
|
|
# Clear cache when settings are updated
|
|
cache.delete('site_settings')
|
|
|
|
def get_email_password(self):
|
|
"""Get decrypted email password."""
|
|
if not self.email_host_password:
|
|
return ''
|
|
try:
|
|
return DataEncryption.decrypt(self.email_host_password)
|
|
except:
|
|
# If decryption fails, return as-is (might be plain text from migration)
|
|
return self.email_host_password
|
|
|
|
def delete(self, *args, **kwargs):
|
|
# Prevent deletion - settings should always exist
|
|
pass
|
|
|
|
@classmethod
|
|
def get_settings(cls):
|
|
"""Get site settings with caching."""
|
|
settings = cache.get('site_settings')
|
|
if settings is None:
|
|
settings, created = cls.objects.get_or_create(pk=1)
|
|
cache.set('site_settings', settings, 3600) # Cache for 1 hour
|
|
return settings
|
|
|
|
|
|
class ScamTag(models.Model):
|
|
"""
|
|
Tags for categorizing scam reports.
|
|
"""
|
|
name = models.CharField(max_length=100, unique=True)
|
|
slug = models.SlugField(max_length=100, unique=True, blank=True)
|
|
description = models.TextField(blank=True)
|
|
color = models.CharField(
|
|
max_length=7,
|
|
default='#007bff',
|
|
help_text='Hex color code for display'
|
|
)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'reports_scamtag'
|
|
verbose_name = 'Scam Tag'
|
|
verbose_name_plural = 'Scam Tags'
|
|
ordering = ['name']
|
|
|
|
def save(self, *args, **kwargs):
|
|
if not self.slug:
|
|
self.slug = slugify(self.name)
|
|
super().save(*args, **kwargs)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class ScamReport(models.Model):
|
|
"""
|
|
Main scam/fraud report model.
|
|
"""
|
|
SCAM_TYPE_CHOICES = [
|
|
('phishing', 'Phishing'),
|
|
('fake_website', 'Fake Website'),
|
|
('romance_scam', 'Romance Scam'),
|
|
('investment_scam', 'Investment Scam'),
|
|
('tech_support_scam', 'Tech Support Scam'),
|
|
('identity_theft', 'Identity Theft'),
|
|
('fake_product', 'Fake Product'),
|
|
('advance_fee', 'Advance Fee Fraud'),
|
|
('other', 'Other'),
|
|
]
|
|
|
|
STATUS_CHOICES = [
|
|
('pending', 'Pending Review'),
|
|
('under_review', 'Under Review'),
|
|
('verified', 'Verified'),
|
|
('rejected', 'Rejected'),
|
|
('archived', 'Archived'),
|
|
]
|
|
|
|
# Reporter information
|
|
reporter = models.ForeignKey(
|
|
User,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='reports'
|
|
)
|
|
is_anonymous = models.BooleanField(
|
|
default=False,
|
|
help_text='Report submitted anonymously'
|
|
)
|
|
|
|
# Report details
|
|
title = models.CharField(max_length=200)
|
|
description = models.TextField()
|
|
scam_type = models.CharField(
|
|
max_length=50,
|
|
choices=SCAM_TYPE_CHOICES,
|
|
default='other'
|
|
)
|
|
|
|
# Reported entities
|
|
reported_url = models.URLField(blank=True, null=True, max_length=500)
|
|
reported_email = models.EmailField(blank=True, null=True)
|
|
reported_phone = models.CharField(max_length=20, blank=True, null=True)
|
|
reported_company = models.CharField(max_length=200, blank=True, null=True)
|
|
|
|
# Evidence
|
|
evidence_files = models.JSONField(
|
|
default=list,
|
|
blank=True,
|
|
help_text='List of file paths for evidence'
|
|
)
|
|
|
|
# Status and verification
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
default='pending'
|
|
)
|
|
verification_score = models.IntegerField(
|
|
default=0,
|
|
help_text='OSINT verification confidence score (0-100)'
|
|
)
|
|
|
|
# Visibility
|
|
is_public = models.BooleanField(
|
|
default=True,
|
|
help_text='Visible in public database'
|
|
)
|
|
is_auto_discovered = models.BooleanField(
|
|
default=False,
|
|
help_text='Automatically discovered by OSINT system'
|
|
)
|
|
|
|
# Metadata
|
|
tags = models.ManyToManyField(ScamTag, blank=True, related_name='reports')
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
verified_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
# IP tracking for anonymous reports
|
|
reporter_ip = models.GenericIPAddressField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
db_table = 'reports_scamreport'
|
|
verbose_name = 'Scam Report'
|
|
verbose_name_plural = 'Scam Reports'
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['status', 'created_at']),
|
|
models.Index(fields=['scam_type', 'status']),
|
|
models.Index(fields=['reported_url']),
|
|
models.Index(fields=['reported_email']),
|
|
models.Index(fields=['reported_phone']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"{self.title} - {self.get_status_display()}"
|
|
|
|
def get_absolute_url(self):
|
|
return reverse('reports:detail', kwargs={'pk': self.pk})
|
|
|
|
def get_reporter_display(self):
|
|
if self.is_anonymous:
|
|
return "Anonymous"
|
|
return self.reporter.username if self.reporter else "Unknown"
|
|
|
|
|
|
class ScamVerification(models.Model):
|
|
"""
|
|
OSINT verification data for scam reports.
|
|
"""
|
|
VERIFICATION_METHOD_CHOICES = [
|
|
('whois', 'WHOIS Lookup'),
|
|
('dns', 'DNS Records'),
|
|
('ssl', 'SSL Certificate'),
|
|
('archive', 'Wayback Machine'),
|
|
('email_check', 'Email Validation'),
|
|
('phone_check', 'Phone Validation'),
|
|
('business_registry', 'Business Registry'),
|
|
('social_media', 'Social Media'),
|
|
('manual', 'Manual Review'),
|
|
]
|
|
|
|
report = models.ForeignKey(
|
|
ScamReport,
|
|
on_delete=models.CASCADE,
|
|
related_name='verifications'
|
|
)
|
|
verification_method = models.CharField(
|
|
max_length=50,
|
|
choices=VERIFICATION_METHOD_CHOICES
|
|
)
|
|
verification_data = models.JSONField(
|
|
default=dict,
|
|
help_text='Raw verification data'
|
|
)
|
|
confidence_score = models.IntegerField(
|
|
default=0,
|
|
help_text='Confidence score for this verification (0-100)'
|
|
)
|
|
verified_by = models.ForeignKey(
|
|
User,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='verifications'
|
|
)
|
|
notes = models.TextField(blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
|
|
class Meta:
|
|
db_table = 'reports_scamverification'
|
|
verbose_name = 'Scam Verification'
|
|
verbose_name_plural = 'Scam Verifications'
|
|
ordering = ['-created_at']
|
|
|
|
def __str__(self):
|
|
return f"Verification for {self.report.title} via {self.get_verification_method_display()}"
|
|
|
|
|
|
class TakedownRequest(models.Model):
|
|
"""
|
|
Request to take down a scam report by the accused party.
|
|
"""
|
|
STATUS_CHOICES = [
|
|
('pending', 'Pending Review'),
|
|
('under_review', 'Under Review'),
|
|
('approved', 'Approved'),
|
|
('rejected', 'Rejected'),
|
|
]
|
|
|
|
report = models.ForeignKey(
|
|
ScamReport,
|
|
on_delete=models.CASCADE,
|
|
related_name='takedown_requests'
|
|
)
|
|
requester_name = models.CharField(
|
|
max_length=200,
|
|
help_text='Име на заявителя'
|
|
)
|
|
requester_email = models.EmailField(
|
|
help_text='Имейл на заявителя'
|
|
)
|
|
requester_phone = models.CharField(
|
|
max_length=50,
|
|
blank=True,
|
|
help_text='Телефон на заявителя (незадължително)'
|
|
)
|
|
reason = models.TextField(
|
|
help_text='Причина за заявката за премахване'
|
|
)
|
|
evidence = models.TextField(
|
|
blank=True,
|
|
help_text='Доказателства или допълнителна информация'
|
|
)
|
|
status = models.CharField(
|
|
max_length=20,
|
|
choices=STATUS_CHOICES,
|
|
default='pending'
|
|
)
|
|
reviewed_by = models.ForeignKey(
|
|
User,
|
|
on_delete=models.SET_NULL,
|
|
null=True,
|
|
blank=True,
|
|
related_name='reviewed_takedown_requests',
|
|
limit_choices_to={'role__in': ['moderator', 'admin']}
|
|
)
|
|
review_notes = models.TextField(
|
|
blank=True,
|
|
help_text='Бележки от модератора'
|
|
)
|
|
ip_address = models.GenericIPAddressField(null=True, blank=True)
|
|
user_agent = models.TextField(blank=True)
|
|
created_at = models.DateTimeField(auto_now_add=True)
|
|
updated_at = models.DateTimeField(auto_now=True)
|
|
reviewed_at = models.DateTimeField(null=True, blank=True)
|
|
|
|
class Meta:
|
|
db_table = 'reports_takedownrequest'
|
|
verbose_name = 'Заявка за Премахване'
|
|
verbose_name_plural = 'Заявки за Премахване'
|
|
ordering = ['-created_at']
|
|
indexes = [
|
|
models.Index(fields=['report', 'status']),
|
|
models.Index(fields=['status', 'created_at']),
|
|
]
|
|
|
|
def __str__(self):
|
|
return f"Takedown request for {self.report.title} by {self.requester_name}"
|