Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
a923c30
added cyber security dashboard (phase 1)
Nachiket-Roy Nov 27, 2025
d645275
added cyber security dashboard (phase 1)
Nachiket-Roy Nov 27, 2025
b680ad3
chore : ordering wth rank
Nachiket-Roy Nov 27, 2025
9163939
chore : harden csv export
Nachiket-Roy Nov 27, 2025
13a2193
chore : csv harden refined
Nachiket-Roy Nov 27, 2025
d79e840
conflicting migration resolved
Nachiket-Roy Nov 28, 2025
6ed2d0f
reviewer leaderboard logic fixed
Nachiket-Roy Nov 28, 2025
27ec274
Added indexes and automatic timestamp management
Nachiket-Roy Nov 28, 2025
d7b6a01
prevent csv injection, added logger and rate-limit
Nachiket-Roy Nov 29, 2025
d604818
rate limit fixed
Nachiket-Roy Nov 29, 2025
5fa95d0
NameError fixed
Nachiket-Roy Nov 29, 2025
b1c8522
removed sort logic
Nachiket-Roy Nov 29, 2025
822a9b1
contributor counted and bot excluded
Nachiket-Roy Nov 29, 2025
f30681e
Hardcoded label resolved
Nachiket-Roy Nov 30, 2025
ca007f1
updated permission class
Nachiket-Roy Dec 2, 2025
e1baecc
security label enforced
Nachiket-Roy Dec 2, 2025
b80a9b6
response improved
Nachiket-Roy Dec 2, 2025
bca77cf
migration conflict resolved
Nachiket-Roy Dec 4, 2025
bd9bf73
migration conflict resolved
Nachiket-Roy Dec 4, 2025
2fcbf6c
merge conflict resolved
Nachiket-Roy Dec 13, 2025
afd9c3e
chore : security issues fixed
Nachiket-Roy Dec 4, 2025
65c6e48
NameError fixed
Nachiket-Roy Dec 4, 2025
e27c722
pre-commit fixed
Nachiket-Roy Dec 4, 2025
de1ceb2
fresh migration created
Nachiket-Roy Dec 4, 2025
f9fee98
race condition and authorization fixed
Nachiket-Roy Dec 4, 2025
9114d31
dead code removed
Nachiket-Roy Dec 4, 2025
b59c403
Nachiket-Roy Dec 4, 2025
af75d72
csv sanitization
Nachiket-Roy Dec 4, 2025
b194086
csv export filter resolved
Nachiket-Roy Dec 4, 2025
c7691d3
removed hardcoded label
Nachiket-Roy Dec 4, 2025
587f138
fixed form validation
Nachiket-Roy Dec 5, 2025
cf0d561
nitpick solved
Nachiket-Roy Dec 5, 2025
2f1bdba
chore: nitpick resolved
Nachiket-Roy Dec 5, 2025
3f4ce1e
migration updated
Nachiket-Roy Dec 6, 2025
4978fd4
signal register
Nachiket-Roy Dec 6, 2025
7fb59d9
from Middleware → Thread-local → Signal → History creation to View → …
Nachiket-Roy Dec 13, 2025
c34ad20
Merge branch 'main' into feature/dashboard
Nachiket-Roy Dec 13, 2025
4b00b99
import fixed
Nachiket-Roy Dec 13, 2025
9cbd4b3
migration corrected
Nachiket-Roy Dec 13, 2025
2daf854
duplicate securityincidentview resolved
Nachiket-Roy Dec 13, 2025
6df63ed
added missing description field
Nachiket-Roy Dec 13, 2025
a0e7523
nitpick
Nachiket-Roy Dec 13, 2025
824f22f
nitpick
Nachiket-Roy Dec 13, 2025
ad2b487
nitpick
Nachiket-Roy Dec 13, 2025
9d9251f
corrected incident_form
Nachiket-Roy Dec 13, 2025
67be17c
dead code removed
Nachiket-Roy Dec 13, 2025
3199439
template error fixed
Nachiket-Roy Dec 13, 2025
72bcf59
added missing import
Nachiket-Roy Dec 13, 2025
4bb6590
wire up history
Nachiket-Roy Dec 13, 2025
5e4ccd7
nitpick
Nachiket-Roy Dec 13, 2025
65df06f
fix : Export fails: missing
Nachiket-Roy Dec 13, 2025
6fdf411
handle error gracefully
Nachiket-Roy Dec 13, 2025
fd26bdd
fixed js broken syntax
Nachiket-Roy Dec 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions blt/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
ProjectViewSet,
PublicJobListViewSet,
SearchHistoryApiView,
SecurityIncidentViewSet,
StatsApiViewset,
TagApiViewset,
TimeLogViewSet,
Expand Down Expand Up @@ -309,6 +310,12 @@
)
from website.views.queue import queue_list, update_txid
from website.views.repo import RepoListView, add_repo, refresh_repo_data
from website.views.security import SecurityDashboardView
from website.views.security_incidents import (
SecurityIncidentCreateView,
SecurityIncidentDetailView,
SecurityIncidentUpdateView,
)
from website.views.Simulation import dashboard, lab_detail, submit_answer, task_detail
from website.views.slack_handlers import slack_commands, slack_events
from website.views.slackbot import slack_landing_page
Expand Down Expand Up @@ -398,6 +405,7 @@
router.register(r"activitylogs", ActivityLogViewSet, basename="activitylogs")
router.register(r"organizations", OrganizationViewSet, basename="organizations")
router.register(r"jobs", JobViewSet, basename="jobs")
router.register(r"security-incidents", SecurityIncidentViewSet, basename="securityincident")

handler404 = "website.views.core.handler404"
handler500 = "website.views.core.handler500"
Expand Down Expand Up @@ -1240,6 +1248,10 @@
path("api/v1/bugs/check-duplicate/", CheckDuplicateBugApiView.as_view(), name="api_check_duplicate_bug"),
path("api/v1/bugs/find-similar/", FindSimilarBugsApiView.as_view(), name="api_find_similar_bugs"),
path("api/v1/search-history/", SearchHistoryApiView.as_view(), name="search_history_api"),
path("security/dashboard/", SecurityDashboardView.as_view(), name="security_dashboard"),
path("security/incidents/add/", SecurityIncidentCreateView.as_view(), name="security_incident_add"),
path("security/incidents/<int:pk>/", SecurityIncidentDetailView.as_view(), name="security_incident_detail"),
path("security/incidents/<int:pk>/edit/", SecurityIncidentUpdateView.as_view(), name="security_incident_edit"),
]

if settings.DEBUG:
Expand Down
30 changes: 29 additions & 1 deletion website/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
from rest_framework.decorators import action, api_view, permission_classes
from rest_framework.exceptions import NotFound, ParseError, PermissionDenied
from rest_framework.pagination import PageNumberPagination
from rest_framework.permissions import AllowAny, IsAuthenticated, IsAuthenticatedOrReadOnly
from rest_framework.permissions import AllowAny, IsAdminUser, IsAuthenticated, IsAuthenticatedOrReadOnly
from rest_framework.response import Response
from rest_framework.views import APIView

Expand All @@ -49,6 +49,7 @@
Project,
Repo,
SearchHistory,
SecurityIncident,
Tag,
TimeLog,
Token,
Expand All @@ -68,6 +69,7 @@
ProjectSerializer,
RepoSerializer,
SearchHistorySerializer,
SecurityIncidentSerializer,
TagSerializer,
TimeLogSerializer,
UserProfileSerializer,
Expand Down Expand Up @@ -1445,6 +1447,32 @@ def trademark_search_api(request):
)


# Security Incident API
class SecurityIncidentViewSet(viewsets.ModelViewSet):
authentication_classes = [SessionAuthentication, TokenAuthentication]
serializer_class = SecurityIncidentSerializer
permission_classes = [IsAdminUser]
queryset = SecurityIncident.objects.all()

def get_queryset(self):
queryset = self.queryset # Use class-level queryset

request = self.request
severity = request.query_params.get("severity")
status = request.query_params.get("status")

allowed_severities = [choice[0] for choice in SecurityIncident.Severity.choices]
allowed_statuses = [choice[0] for choice in SecurityIncident.Status.choices]

if severity in allowed_severities:
queryset = queryset.filter(severity=severity)

if status in allowed_statuses:
queryset = queryset.filter(status=status)

return queryset


class CheckDuplicateBugApiView(APIView):
"""
API endpoint to check for duplicate bug reports before submission.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Generated by Django 5.2.9 on 2025-12-13 12:33

import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("website", "0261_add_connected_action_type"),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]

operations = [
migrations.CreateModel(
name="SecurityIncident",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("title", models.CharField(max_length=255)),
(
"severity",
models.CharField(
choices=[("low", "Low"), ("medium", "Medium"), ("high", "High"), ("critical", "Critical")],
default="medium",
max_length=20,
),
),
(
"status",
models.CharField(
choices=[("open", "Open"), ("investigating", "Investigating"), ("resolved", "Resolved")],
default="open",
max_length=20,
),
),
("affected_systems", models.TextField(blank=True)),
("description", models.TextField(blank=True, help_text="Detailed description of the incident")),
("created_at", models.DateTimeField(auto_now_add=True)),
("resolved_at", models.DateTimeField(blank=True, null=True)),
],
options={
"ordering": ["-created_at"],
},
),
migrations.CreateModel(
name="SecurityIncidentHistory",
fields=[
("id", models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name="ID")),
("field_name", models.CharField(max_length=100)),
("old_value", models.TextField(blank=True, null=True)),
("new_value", models.TextField(blank=True, null=True)),
("changed_at", models.DateTimeField(auto_now_add=True)),
],
options={
"ordering": ["-changed_at"],
},
),
migrations.RenameIndex(
model_name="socialaccountreward",
new_name="website_soc_user_id_fb0840_idx",
old_name="website_soc_user_id_provid_idx",
),
migrations.AddField(
model_name="securityincident",
name="reporter",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="reported_incidents",
to=settings.AUTH_USER_MODEL,
),
),
migrations.AddField(
model_name="securityincidenthistory",
name="changed_by",
field=models.ForeignKey(
blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL
),
),
migrations.AddField(
model_name="securityincidenthistory",
name="incident",
field=models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, related_name="history", to="website.securityincident"
),
),
migrations.AddIndex(
model_name="securityincident",
index=models.Index(fields=["severity"], name="incident_severity_idx"),
),
migrations.AddIndex(
model_name="securityincident",
index=models.Index(fields=["status"], name="incident_status_idx"),
),
migrations.AddIndex(
model_name="securityincident",
index=models.Index(fields=["-created_at"], name="incident_created_idx"),
),
migrations.AddIndex(
model_name="securityincidenthistory",
index=models.Index(fields=["incident", "-changed_at"], name="history_incident_changedat_idx"),
),
]
88 changes: 88 additions & 0 deletions website/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2887,7 +2887,22 @@ def get_reviewer_leaderboard(self):

# Group by reviewer and count reviews
leaderboard = {}
seen = set()

for review in reviews:
if review.reviewer_id:
# Registered user → unique ID
reviewer_identity = f"user_{review.reviewer_id}"
else:
# Contributor → unique ID even if names match
reviewer_identity = f"contrib_{review.reviewer_contributor_id}"

reviewer_key = (review.pull_request_id, reviewer_identity)
# Skip duplicate reviews for same PR+reviewer
if reviewer_key in seen:
continue
seen.add(reviewer_key)

if review.reviewer:
# Registered user reviewer
user_id = review.reviewer.user.id
Expand Down Expand Up @@ -3545,3 +3560,76 @@ class Meta:

def __str__(self):
return f"{self.user.username} - {self.get_transaction_type_display()} - {self.amount} BACON"


class SecurityIncident(models.Model):
class Severity(models.TextChoices):
LOW = "low", "Low"
MEDIUM = "medium", "Medium"
HIGH = "high", "High"
CRITICAL = "critical", "Critical"

class Status(models.TextChoices):
OPEN = "open", "Open"
INVESTIGATING = "investigating", "Investigating"
RESOLVED = "resolved", "Resolved"

title = models.CharField(max_length=255)
severity = models.CharField(
max_length=20,
choices=Severity.choices,
default=Severity.MEDIUM,
)
status = models.CharField(
max_length=20,
choices=Status.choices,
default=Status.OPEN,
)
affected_systems = models.TextField(blank=True)
description = models.TextField(blank=True, help_text="Detailed description of the incident")
reporter = models.ForeignKey(
User, on_delete=models.SET_NULL, null=True, blank=True, related_name="reported_incidents"
)
created_at = models.DateTimeField(auto_now_add=True)
resolved_at = models.DateTimeField(null=True, blank=True)

# NEW AUTO-FIELDS & ENHANCEMENTS
def __str__(self):
return f"{self.title} ({self.get_severity_display()}) - {self.get_status_display()}"

def save(self, *args, **kwargs):
"""
Automatically set or clear resolved_at timestamp based on status.
"""
if self.status == self.Status.RESOLVED and not self.resolved_at:
self.resolved_at = timezone.now()
elif self.status != self.Status.RESOLVED and self.resolved_at:
self.resolved_at = None

super().save(*args, **kwargs)

class Meta:
ordering = ["-created_at"]
indexes = [
models.Index(fields=["severity"], name="incident_severity_idx"),
models.Index(fields=["status"], name="incident_status_idx"),
models.Index(fields=["-created_at"], name="incident_created_idx"),
]


class SecurityIncidentHistory(models.Model):
incident = models.ForeignKey(SecurityIncident, on_delete=models.CASCADE, related_name="history")
field_name = models.CharField(max_length=100)
old_value = models.TextField(null=True, blank=True)
new_value = models.TextField(null=True, blank=True)
changed_by = models.ForeignKey(User, on_delete=models.SET_NULL, null=True, blank=True)
changed_at = models.DateTimeField(auto_now_add=True)

class Meta:
ordering = ["-changed_at"]
indexes = [
models.Index(
fields=["incident", "-changed_at"],
name="history_incident_changedat_idx",
),
]
47 changes: 47 additions & 0 deletions website/security_incident_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from django import forms

from website.models import SecurityIncident


class SecurityIncidentForm(forms.ModelForm):
class Meta:
model = SecurityIncident
fields = [
"title",
"severity",
"status",
"affected_systems",
"description",
]

widgets = {
"title": forms.TextInput(
attrs={
"class": "form-control",
"placeholder": "Enter incident title",
}
),
"severity": forms.Select(attrs={"class": "form-select"}),
"status": forms.Select(attrs={"class": "form-select"}),
"affected_systems": forms.Textarea(
attrs={
"class": "form-control",
"placeholder": "List affected systems (comma-separated)",
"rows": 4,
}
),
"description": forms.Textarea(
attrs={
"class": "form-control",
"placeholder": "Detailed description of the incident",
"rows": 6,
}
),
}

def clean_affected_systems(self):
"""Normalize comma-separated affected_systems (strip items, drop empties)."""
raw = self.cleaned_data.get("affected_systems", "") or ""
parts = [p.strip() for p in raw.split(",")]
normalized = ", ".join(p for p in parts if p)
return normalized
17 changes: 17 additions & 0 deletions website/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
Project,
Repo,
SearchHistory,
SecurityIncident,
Tag,
TimeLog,
Trademark,
Expand Down Expand Up @@ -305,3 +306,19 @@ class Meta:
model = SearchHistory
fields = ["id", "query", "search_type", "timestamp", "result_count"]
read_only_fields = ["id", "timestamp"]


class SecurityIncidentSerializer(serializers.ModelSerializer):
class Meta:
model = SecurityIncident
fields = [
"id",
"title",
"description",
"severity",
"status",
"affected_systems",
"created_at",
"resolved_at",
]
read_only_fields = ["id", "created_at", "resolved_at"]
Loading
Loading