diff --git a/CHANGELOG.md b/CHANGELOG.md index fb2c7de..d0657b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,55 @@ CHANGELOG VINCE Coordination platform code + +## Description +VINCE Coordination platform code + +Version 3.0.21 2025-06-30 + +* updated numerous links to CVD documentation, etc. (Internal-816) +* added logging for the authentication process (Internal-817) +* modified specifications for spreadsheet used for detailed weekly reports to CISA (Internal-804) + + +Version 3.0.20 2025-06-11 + +* fixed bug affecting redirect after editing case vulnerability information (Internal-810) +* added filtering for the Edit Case form to prevent inactive users from appearing as potential case owners (Internal-811) +* reinstated API endpoints turned off in last release; added database model for logging certain API endpoint access events (Internal-812) +* dependabot update recommendations: `django` 4.2.21 to 4.2.22, `requests` 2.32.0 to 2.32.4 + + +Version 3.0.19 2025-05-06 + +* Turned off certain API endpoints for security review (Internal-807) +* Updated code for CSV files in response to user requests for more fine-grained information (Internal-804) +* Updated link to VINCE Documentation (Internal-808) + + +Version 3.0.18 2025-04-17 + +* Updated code for CSV files in reponse to even more user requests for more fine-grained information (Internal-804) + + +Version 3.0.17 2025-04-17 + +* Updated code for CSV files in reponse to user requests for more fine-grained information (Internal-804) + + +Version 3.0.16 2025-04-17 + +* dependabot update recommendations: `Django` 4.2.17 to 4.2.20, `python-jose` 3.3.0 to 3.4.0 +* Fixed bug preventing certain users from changing their passwords (Internal-800) +* Tweaked logs for Internal-791 +* Added code for CSV files to support preparation of reports for CISA (Internal-804) + + +Version 3.0.15 2025-03-17 + +* Modified code for checking authenticity so as to include extra logs and to bypass false negatives (Internal-791) + + Version 3.0.14 2025-03-17 * Added code for checking authenticity of emails subject to new preprocessing for AWS email integration (Internal-791) diff --git a/README.md b/README.md index ab6d3d1..10044a5 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ Mellon University. * The CERT Guide to Coordinated Vulnerability Disclosure: [https://vuls.cert.org/confluence/display/CVD](https://vuls.cert.org/confluence/display/CVD) * Report a Vulnerability [https://www.kb.cert.org/vuls/report/](https://www.kb.cert.org/vuls/report/) -* VINCE User Documentation: [https://vuls.cert.org/confluence/display/VIN/VINCE+Documentation](https://vuls.cert.org/confluence/display/VIN/VINCE+Documentation) -* Vulnerability Note API Documentation: [https://vuls.cert.org/confluence/display/VIN/Vulnerability+Note+API](https://vuls.cert.org/confluence/display/VIN/Vulnerability+Note+API) -* VINCE API Documentation: [https://vuls.cert.org/confluence/display/VIN/VINCE+API](https://vuls.cert.org/confluence/display/VIN/VINCE+API) +* VINCE User Documentation: [https://certcc.github.io/VINCE-docs/](https://certcc.github.io/VINCE-docs/) +* Vulnerability Note API Documentation: [https://certcc.github.io/VINCE-docs/Vulnerability-Note-API/](https://certcc.github.io/VINCE-docs/Vulnerability-Note-API/) +* VINCE API Documentation: [https://certcc.github.io/VINCE-docs/VINCE-API/](https://certcc.github.io/VINCE-docs/VINCE-API/) ### Bugs and Feature Requests @@ -74,7 +74,6 @@ reduce the risk of exposure. [](https://github.com/CERTCC/VINCE/raw/main/Vince_Infrastructure.png) - ### Local Install 1. Clone the repo diff --git a/bigvince/settings_.py b/bigvince/settings_.py index 82fef99..59d5e0c 100644 --- a/bigvince/settings_.py +++ b/bigvince/settings_.py @@ -54,7 +54,7 @@ ROOT_DIR = environ.Path(__file__) - 3 # any change that requires database migrations is a minor release -VERSION = "3.0.14" +VERSION = "3.0.21" # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ diff --git a/cogauth/backend.py b/cogauth/backend.py index 7fd521b..17a6264 100644 --- a/cogauth/backend.py +++ b/cogauth/backend.py @@ -134,6 +134,7 @@ class CognitoAuthenticate(ModelBackend): def authenticate(self, request, username=None, password=None): ip = vinceutils.get_ip(request) if username and password: + logger.debug(f"CognitoAuthenticate is running with username {username}") cognito_user = CognitoUser( settings.COGNITO_USER_POOL_ID, settings.COGNITO_APP_ID, @@ -147,10 +148,12 @@ def authenticate(self, request, username=None, password=None): logger.debug(f"trying to authenticate {username} from IP {ip}") cognito_user.authenticate(password) except ForceChangePasswordException: + logger.debug(f"ForceChangePasswordException when trying to authenticate {username} from IP {ip}") request.session["FORCEPASSWORD"] = True request.session["username"] = username return None except SoftwareTokenException as e: + logger.debug(f"SoftwareTokenException {e} when trying to authenticate {username} from IP {ip}") request.session["MFAREQUIRED"] = "SOFTWARE_TOKEN_MFA" request.session["username"] = username request.session["MFASession"] = cognito_user.session @@ -158,6 +161,7 @@ def authenticate(self, request, username=None, password=None): request.session.save() return None except SMSMFAException: + logger.debug(f"SMSMFAException when trying to authenticate {username} from IP {ip}") request.session["MFAREQUIRED"] = "SMS_MFA" request.session["username"] = username request.session["MFASession"] = cognito_user.session @@ -194,6 +198,7 @@ def authenticate(self, request, username=None, password=None): # emails for username - so get email and return CognitoUser email = list(filter(lambda email: email["Name"] == "email", user["UserAttributes"]))[0]["Value"] username = email + logger.debug(f"CognitoAuthenticate is running on an ACCESS_TOKEN. username is {username}") cognito_user = CognitoUser( settings.COGNITO_USER_POOL_ID, settings.COGNITO_APP_ID, @@ -207,6 +212,9 @@ def authenticate(self, request, username=None, password=None): cognito_user.refresh_token = request.session["REFRESH_TOKEN"] else: + logger.debug( + "CognitoAuthenticate is running, but it has not found a username/password pair or an ACCESS_TOKEN." + ) headers = {"Content-Type": "application/x-www-form-urlencoded"} data = { "grant_type": "authorization_code", @@ -237,6 +245,9 @@ def authenticate(self, request, username=None, password=None): ) user = client.get_user(AccessToken=access_token) username = user["Username"] + logger.debug( + f"CognitoAuthenticate is running, but it has not found a username/password pair or an ACCESS_TOKEN. Nevertheless, username has been found to be {username}" + ) cognito_user = CognitoUser( settings.COGNITO_USER_POOL_ID, settings.COGNITO_APP_ID, diff --git a/cogauth/views.py b/cogauth/views.py index 6d43154..d471f71 100644 --- a/cogauth/views.py +++ b/cogauth/views.py @@ -838,6 +838,7 @@ def form_valid(self, form): self.request.session["ACCESS_TOKEN"] = tokens["AuthenticationResult"]["AccessToken"] user = authenticate(self.request, username=self.request.session["username"]) if user: + logger.debug(f"user {user} is trying to use the MFA Required screen") del self.request.session["username"] auth_login(self.request, user) self.cognito = get_cognito(self.request) @@ -863,8 +864,10 @@ def form_valid(self, form): logger.debug(f"NEXT URL provided by GET request {next_url}") try: if is_safe_url(next_url, set(settings.ALLOWED_HOSTS), True): + logger.debug(f"{user} wants to access {next_url}, and it has been found to be safe") return redirect(next_url) else: + logger.debug(f"{user} wants to access {next_url}, and it has not been found to be safe") return redirect(settings.LOGIN_REDIRECT_URL) except Exception as e: logger.debug(f"Error in redirection validator {e}") @@ -1004,14 +1007,16 @@ def form_valid(self, form): ip = vinceutils.get_ip(self.request) try: c.change_password(form.cleaned_data["old_password"], form.cleaned_data["new_password1"]) - logger.info(f"Password was updated for {self.request.username} from IP {ip}") + logger.info(f"Password was updated for {self.request.user.username} from IP {ip}") except ParamValidationError: - logger.info(f"Password updated failed for {self.request.username} from IP {ip} - invalid new password") + logger.info( + f"Password updated failed for {self.request.user.username} from IP {ip} - invalid new password" + ) form._errors.setdefault("new_password1", ErrorList(["New password is unacceptable."])) return super().form_invalid(form) except (Boto3Error, ClientError) as e: error_code = e.response["Error"]["Code"] - logger.info(f"Password updated failed for {self.request.username} from IP {ip} - {e} {error_code}") + logger.info(f"Password updated failed for {self.request.user.username} from IP {ip} - {e} {error_code}") if error_code == "NotAuthorizedException": form._errors.setdefault("old_password", ErrorList(["Password is incorrect."])) return super().form_invalid(form) diff --git a/requirements.txt b/requirements.txt index bfd20f5..a4f8099 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,7 +24,7 @@ cryptography==44.0.1 cvelib==1.3.0 Deprecated==1.2.13 dictdiffer==0.9.0 -Django==4.2.17 +Django==4.2.22 django-appconf==1.0.5 django-countries==7.4.2 django-environ==0.9.0 @@ -63,12 +63,12 @@ pyparsing==3.0.9 pyrsistent==0.19.2 python-dateutil==2.8.2 python-gnupg==0.5.0 -python-jose==3.3.0 +python-jose==3.4.0 pytz==2022.6 PyYAML==5.4.1 qrcode==7.3.1 redis==4.5.4 -requests==2.32.0 +requests==2.32.4 rsa==4.7.2 s3transfer==0.6.0 segno==1.5.2 diff --git a/vince/forms.py b/vince/forms.py index 83552b9..2682e06 100644 --- a/vince/forms.py +++ b/vince/forms.py @@ -1699,10 +1699,15 @@ class Meta: def get_group_choices(self, user): return [(q.id, q.name) for q in user.groups.exclude(groupsettings__contact__isnull=True)] + def get_owner_choices(self): + + return [(u.id, u.email) for u in User.objects.using("default").filter(is_active=True)] + def __init__(self, *args, **kwargs): user = kwargs.pop("user") super(EditCaseForm, self).__init__(*args, **kwargs) self.fields["team_owner"].choices = self.get_group_choices(user) + self.fields["owner"].choices = self.get_owner_choices() class AssignTicketTeamForm(forms.Form): diff --git a/vince/lib.py b/vince/lib.py index 29cf719..5c91e14 100644 --- a/vince/lib.py +++ b/vince/lib.py @@ -2364,7 +2364,7 @@ def verify_authenticity_header(msg, key, headers): match = hmac.compare_digest(value, expected_value) if not match: logger.warn( - "match did not happen correctly. expected_value is {expected_value} and actual value is {value}" + f"match did not happen correctly. expected_value is {expected_value} and actual value is {value}" ) return match @@ -3152,6 +3152,8 @@ def prepare_and_send_weekly_report(): week = oneweekago.isocalendar()[1] weekstartdate = date.fromisocalendar(year, week, 1) weekenddate = date.fromisocalendar(year, week, 7) + # for testing: + # daterangeend = datetime.now() daterangeend = weekenddate + timedelta(days=1) # examine the GroupSettings model, looking for groups that have weekly="on" @@ -3178,6 +3180,7 @@ def prepare_and_send_weekly_report(): ticket__queue__in=my_queues, ).exclude(ticket__case__isnull=False) tickets = Ticket.objects.filter(queue__in=my_queues, created__range=[weekstartdate, daterangeend]) + logger.debug(f"when processing the weekly report data, tickets is found to be {tickets}") closed_tickets = tickets.filter(status=Ticket.CLOSED_STATUS) new_cases = VulnerabilityCase.objects.filter( created__range=[weekstartdate, daterangeend], team_owner=my_team @@ -3219,6 +3222,8 @@ def prepare_and_send_weekly_report(): "case_emails_distinct": case_emails.order_by("ticket__case__id").distinct("ticket__case__id").count(), "total_emails": ticket_emails.count() + case_emails.count(), "total_tickets": tickets.count(), + "tickets": tickets, + "closed_tickets": closed_tickets, "ticket_stats": tickets.values("queue__title") .order_by("queue__title") .annotate(count=Count("queue__title")) @@ -3247,15 +3252,7 @@ def prepare_and_send_weekly_report(): ), } - logger.debug("context complete") - logger.debug("weeklyreport context is") - logger.debug(context) - - # # This is just for testing: - # # weekstartdate = date.today() - # # daterangeend = weekstartdate + timedelta(days=1) - # # context['weekstartdate'] = weekstartdate - # # context['weekenddate'] = weekstartdate + timedelta(days=1) + logger.debug(f"weeklyreport context is {context}") if groupid == 1: total_ai_ml_crs = ( diff --git a/vince/templates/vince/base.html b/vince/templates/vince/base.html index 7afb823..fd448c1 100644 --- a/vince/templates/vince/base.html +++ b/vince/templates/vince/base.html @@ -307,7 +307,7 @@
Vulnerability INformation and Coordination Environment
-->
© {% now "Y" %} Carnegie Mellon University
  • Disclosure Policy
  • -
  • Terms of Use
  • +
  • Terms of Use
  • V.{{ VERSION }}
  • diff --git a/vince/templates/vince/index.html b/vince/templates/vince/index.html index f967888..86b91ab 100644 --- a/vince/templates/vince/index.html +++ b/vince/templates/vince/index.html @@ -6,7 +6,7 @@

    VINCE

    -

    Welcome to the Vulnerability Information and Coordination Environment (VINCE). If you are a vendor and would like to communicate with us about a vulnerability or update your contact information, please create an account or sign in. You can also report a vulnerability to us, with or without a VINCE account. +

    Welcome to the Vulnerability Information and Coordination Environment (VINCE). If you are a vendor and would like to communicate with us about a vulnerability or update your contact information, please create an account or sign in. You can also report a vulnerability to us, with or without a VINCE account. For more information see the VINCE Documentation site.

    Create an Account diff --git a/vince/templates/vince/printweeklyreport.html b/vince/templates/vince/printweeklyreport.html index 6ca81ab..ef6e9a3 100644 --- a/vince/templates/vince/printweeklyreport.html +++ b/vince/templates/vince/printweeklyreport.html @@ -151,26 +151,27 @@

    Cases

    New Cases

    - - - + + + {% for note in case_stats.new_cases %} - - + + {% endfor %}
    Case NameDate Created
    Case NameDate Created
    - {% if note|case_access:user %}{{ note.vutitle }}{% else %}{{ note.vu_vuid }}{% endif %} - - {{ note.created|date:"Y-m-d" }} - + {% if note|case_access:user %}{{ note.vutitle }}{% else %}{{ note.vu_vuid }}{% endif %} + + {{ note.created|date:"Y-m-d" }} +
    {% endif %} + {% if case_stats.deactive_cases %}

    Deactivated Cases

    - + {% for note in case_stats.deactive_cases %} @@ -207,19 +208,19 @@

    Reactivated Cases

    {% endif %} {% if fwd_reports %} -

    Forwarded Reports

    -
    Case NameCase Name Date Deactivated
    - - - - - {% for r in fwd_reports %} - - - - - {% endfor %} -
    TitleDate
    [{{ r.ticket.queue }}-{{ r.ticket.id }}] {{ r.ticket.title }} {{ r.date|date:"Y-m-d" }}
    +

    Forwarded Reports

    + + + + + + {% for r in fwd_reports %} + + + + + {% endfor %} +
    TitleDate
    [{{ r.ticket.queue }}-{{ r.ticket.id }}] {{ r.ticket.title }} {{ r.date|date:"Y-m-d" }}
    {% endif %} @@ -236,6 +237,7 @@

    Tickets Opened

    {% endfor %} + {% endif %} @@ -253,6 +255,7 @@

    Tickets Closed

    {% endfor %} + {% endif %} {% if my_team.name == "CERT/CC" %} diff --git a/vince/templates/vince/reports.html b/vince/templates/vince/reports.html index 03bd47c..2d76602 100644 --- a/vince/templates/vince/reports.html +++ b/vince/templates/vince/reports.html @@ -180,6 +180,36 @@

    +
    +

    Download Weekly Reports in CSV Format

    +
    +
    +

    + + + + + {% comment "not using this currently" %}
    diff --git a/vince/urls.py b/vince/urls.py index a781703..6c0f751 100644 --- a/vince/urls.py +++ b/vince/urls.py @@ -221,6 +221,31 @@ views.PrintWeeklyReportsView.as_view(), name="printweeklyreport", ), + re_path( + "reports/weekly/csv/new_reports_submissions", + views.WeeklyCSVNewReportsOrSubmissionsView.as_view(), + name="weekly_csv_new_reports_submissions", + ), + re_path( + "reports/weekly/csv/active_cases_in_progress", + views.WeeklyCSVActiveCasesInProgressView.as_view(), + name="weekly_csv_active_cases_in_progress", + ), + re_path( + "reports/weekly/csv/completed_cases", + views.WeeklyCSVCompletedCasesView.as_view(), + name="weekly_csv_completed_cases", + ), + re_path( + "reports/weekly/csv/declined_cases", + views.WeeklyCSVDeclinedCasesView.as_view(), + name="weekly_csv_declined_cases", + ), + re_path( + "reports/weekly/csv/cases_published_cves", + views.WeeklyCSVCasesWithPublishedCVEsView.as_view(), + name="weekly_csv_cases_published_cves", + ), path("triage/", views.TriageView.as_view(), name="triage"), re_path(r"^triage/(?P[0-9]+)/$", views.TriageView.as_view(), name="triage"), path("reports/casesnovendors/", views.CasesWithoutVendorsReport.as_view(), name="cnovreport"), diff --git a/vince/views.py b/vince/views.py index ec62cfa..17dbf93 100644 --- a/vince/views.py +++ b/vince/views.py @@ -47,6 +47,7 @@ from django.urls import reverse, reverse_lazy from cvelib import cve_api as cvelib import pytz +import csv import email from django.template.loader import get_template from vince.lib import ( @@ -13237,7 +13238,8 @@ def form_valid(self, form): ): return HttpResponseRedirect(self.request.META.get("HTTP_REFERER") + "#vuls") else: - return redirect("vince:editvuls", vul.case.id) + return redirect(reverse("vince:case", args=[vul.case.id]) + "#vuls") + def form_invalid(self, form): logger.debug(f"{self.__class__.__name__} errors: {form.errors}") @@ -14204,7 +14206,7 @@ def post(self, request, *args, **kwargs): self.request.META.get("HTTP_REFERER"), set(settings.ALLOWED_HOSTS), True ): return HttpResponseRedirect(self.request.META.get("HTTP_REFERER") + "#vuls") - return redirect("vince:editvuls", case.id) + return redirect(reverse("vince:case", args=[vul.case.id]) + "#vuls") class VulnerabilityDetailView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.DetailView): @@ -14892,6 +14894,275 @@ def get_context_data(self, **kwargs): return context +class WeeklyCSVNewReportsOrSubmissionsView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): + login_url = "vince:login" + + def test_func(self): + return is_in_group_vincetrack(self.request.user) + + def get(self, request, *args, **kwargs): + today = date.today() + oneweekago = date.today() - timedelta(days=9) + + new_certcc_cases = VulnerabilityCase.objects.filter( + created__range=[oneweekago, today], team_owner__name="CERT/CC" + ).order_by("created") + + new_unattached_tickets = [ + ticket for ticket in Ticket.objects.filter(created__range=[oneweekago, today], case=None, depends_on=None) + ] + + new_unattached_ticket_ids = [ticket.id for ticket in new_unattached_tickets] + + declined_certcc_crtickets = [ + ticket + for ticket in Ticket.objects.filter( + queue__queue_type=TicketQueue.CASE_REQUEST_QUEUE, + modified__range=[oneweekago, today], + status=Ticket.CLOSED_STATUS, + ).exclude(id__in=new_unattached_ticket_ids) + if ticket.assigned_to != None and get_my_team(ticket.assigned_to).name == "CERT/CC" + ] + + new_tickets_and_newly_declined_tickets = new_unattached_tickets + declined_certcc_crtickets + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = ( + f'attachment; filename="new_reports_or_submissions_{oneweekago}_to_{today}.csv"' + ) + + writer = csv.writer(response) + + # Write header row + writer.writerow( + [ + "Case ID", + "Case Name", + "Summary", + "Initial Triage Actions", + "Assigned Personnel/Team", + "Justification if Declined", + ] + ) + + for case in new_certcc_cases: + writer.writerow( + [ + f"VU#{case.vuid}", + case.title, + case.summary, + "", + case.team_owner.name, + ] + ) + + for ticket in new_tickets_and_newly_declined_tickets: + if ticket.queue.title == "CR": + assigned_team = "" + justification_if_declined = "" + try: + assigned_team = get_my_team(ticket.assigned_to).name + except: + assigned_team = "no assigned team found" + resolution = "" + try: + resolution = ticket.resolution + except: + resolution = "no resolution" + try: + # justification_if_declined = ticket.get_close_reason_display() + # In the fulness of time, we will put content in this space that is determined by the template used to + # respond to the reporter. For now we have: + justification_if_declined = "" + except: + justification_if_declined = "" + writer.writerow( + [ + f"{ticket.queue}-{ticket.id}", + ticket.title, + resolution, + "", + assigned_team, + justification_if_declined, + ] + ) + + writer.writerow([f"{len(new_unattached_tickets)} tickets entered triage in the last week."]) + + return response + + +class WeeklyCSVActiveCasesInProgressView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): + login_url = "vince:login" + + def test_func(self): + return is_in_group_vincetrack(self.request.user) + + def get(self, request, *args, **kwargs): + today = date.today() + oneweekago = date.today() - timedelta(days=9) + + active_cases = VulnerabilityCase.objects.filter( + status=VulnerabilityCase.ACTIVE_STATUS, created__lt=oneweekago, team_owner__name="CERT/CC" + ).order_by("owner_id") + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = ( + f'attachment; filename="active_cases_in_progress_{oneweekago}_to_{today}.csv"' + ) + + writer = csv.writer(response) + + # Write header row + writer.writerow( + ["Case ID", "Case Name", "Date of Last Action", "Next Steps", "Blockers", "Estimated Completion"] + ) + + for case in active_cases: + last_modified = "" + try: + last_modified = case.modified.strftime("%m/%d/%Y") + except: + last_modified = "never modified" + due_date = "" + try: + due_date = case.due_date.strftime("%m/%d/%Y") + except: + due_date = "no due date" + writer.writerow([f"VU#{case.vuid}", case.title, last_modified, "", "", due_date]) + + return response + + +class WeeklyCSVCompletedCasesView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): + login_url = "vince:login" + + def test_func(self): + return is_in_group_vincetrack(self.request.user) + + def get(self, request, *args, **kwargs): + today = date.today() + oneweekago = date.today() - timedelta(days=9) + + case_closures = ( + CaseAction.objects.filter( + title__icontains="changed status of case from Active to Inactive", + date__range=[oneweekago, today], + case__team_owner__name="CERT/CC", + ) + .select_related("case") + .order_by("case") + .distinct("case") + ) + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = f'attachment; filename="completed_cases_{oneweekago}_to_{today}.csv"' + + writer = csv.writer(response) + + # Write header row + writer.writerow(["Case ID", "Case Name", "Final Outcome", "Lessons Learned", "Follow-Up Actions"]) + + for closure in case_closures: + writer.writerow( + [ + f"VU#{closure.case.vuid}", + closure.case.title, + ] + ) + + return response + + +class WeeklyCSVDeclinedCasesView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): + login_url = "vince:login" + + def test_func(self): + return is_in_group_vincetrack(self.request.user) + + def get(self, request, *args, **kwargs): + today = date.today() + oneweekago = date.today() - timedelta(days=9) + + declined_certcc_crtickets = [ + ticket + for ticket in Ticket.objects.filter( + queue__queue_type=TicketQueue.CASE_REQUEST_QUEUE, + modified__range=[oneweekago, today], + status=Ticket.CLOSED_STATUS, + ) + if ticket.assigned_to != None and get_my_team(ticket.assigned_to).name == "CERT/CC" + ] + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = f'attachment; filename="declined_cases_{oneweekago}_to_{today}.csv"' + + writer = csv.writer(response) + + # Write header row + writer.writerow( + ["Case ID", "Case Name", "Justification for Declining", "Work Performed Before Decision", "Date Declined"] + ) + + for ticket in declined_certcc_crtickets: + most_recent_closure_date = "" + try: + most_recent_closure_date = ( + ticket.get_actions().filter(title="Closed").latest("date").date.strftime("%m/%d/%Y") + ) + except: + most_recent_closure_date = "closure date not found" + logger.debug(f"ticket {ticket.id} is closed, but its closure date was not found") + writer.writerow( + [f"CR-{ticket.id}", ticket.title, ticket.get_close_reason_display(), "", most_recent_closure_date] + ) + + return response + + +class WeeklyCSVCasesWithPublishedCVEsView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): + login_url = "vince:login" + + def test_func(self): + return is_in_group_vincetrack(self.request.user) + + def get(self, request, *args, **kwargs): + today = date.today() + oneweekago = date.today() - timedelta(days=9) + + certcc_cve_cases = [ + case + for case in VulnerabilityCase.objects.filter( + status=VulnerabilityCase.ACTIVE_STATUS, team_owner__name="CERT/CC" + ) + if case.get_cves() + ] + + # for case in cve_cases: + # case.cves = case.get_cves() + + response = HttpResponse(content_type="text/csv") + response["Content-Disposition"] = f'attachment; filename="cases_with_cves_{oneweekago}_to_{today}.csv"' + + writer = csv.writer(response) + + # Write header row + writer.writerow(["Case ID", "Case Name", "CVE Published (Yes/No)", "CVE Number", "Summary of Findings"]) + + for case in certcc_cve_cases: + for cve in case.get_cves(): + writer.writerow( + [ + f"VU#{case.vuid}", + case.title, + "", + cve, + ] + ) + + return response + + class PrintWeeklyReportsView(LoginRequiredMixin, TokenMixin, UserPassesTestMixin, generic.TemplateView): login_url = "vince:login" template_name = "vince/printweeklyreport.html" @@ -16802,7 +17073,7 @@ def get_context_data(self, **kwargs): comments = fup.ticket.description.splitlines() email_from = fup.ticket.submitter_email match = re.search( - "New [E|e]mail( received)?(?P from ([ \x20-\x7E]+))? to (?P.+?(?=CERT))", + "New [E|e]mail( received)?(?P from ([ \x20-\x7e]+))? to (?P.+?(?=CERT))", fup.title, ) if match: diff --git a/vincepub/templates/vincepub/base.html b/vincepub/templates/vincepub/base.html new file mode 100644 index 0000000..1dc44b9 --- /dev/null +++ b/vincepub/templates/vincepub/base.html @@ -0,0 +1,286 @@ + + + + + + + + + + + {% block extra_head_tags %} + + + CERT Vulnerability Notes Database + + + + + + + + + + {% endblock %} + + {% load staticfiles %} + + + + + + + + + + + {% block js %} + + + + + + + + + {% endblock %} + + + + + + + + + + + search + + + + menu + + + + + + icon-carat-right + + + + cmu-wordmark + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +{% block notice %}{% endblock %} +
    + +
    + + +
    +
    + + +
    + +
    + +
    +
    +
    +
    + + +
    +
    +
    +
    +
    +
    +
    + + + +
    +
    +

    CERT Coordination Center

    +
    +
    + + + +
    + + {% block content %} + {% endblock %} + +
    + {% block footer %} + + +
    +
    + + + +
    +
    +
    +

    Sponsored by CISA.

    +
    +
    +
    + + + +
    +
    + + + +{% endblock %} + + + + diff --git a/vincepub/templates/vincepub/disclosure_guidance.html b/vincepub/templates/vincepub/disclosure_guidance.html new file mode 100644 index 0000000..e577518 --- /dev/null +++ b/vincepub/templates/vincepub/disclosure_guidance.html @@ -0,0 +1,46 @@ +{% extends "vincepub/base.html" %} + +{% block content %} +
    + +
    +
    +
    +
    +
    +

    Vulnerability Disclosure Guidance

    +
    +
    +
    +
    +
    +

    What is Vulnerability Coordination?

    +

    During vulnerability coordination, multiple stakeholders analyze a vulnerability to be able to disclose it to the public and provide guidance on how to mitigate or fix it.

    +

    + A vulnerability is difficult to define. It can be thought of as a flaw in software or hardware components that allows an attacker to perform actions that wouldn't normally be allowed. The impact of such vulnerabilities varies greatly. They may allow the attacker to learn someone's private email address, take control of a computer, or even cause physical damage and bodily injury.

    +

    At CERT/CC, our goal is to coordinate with the various stakeholders and make sure the vulnerability is addressed accordingly and that the correct information reaches the public.

    + Read more +
    +
    +
    +
    + +
    + +{% endblock %} + diff --git a/vincepub/templates/vincepub/example.html b/vincepub/templates/vincepub/example.html new file mode 100644 index 0000000..f5a7acc --- /dev/null +++ b/vincepub/templates/vincepub/example.html @@ -0,0 +1,191 @@ +{% extends "vulcoord/base.html" %} +{% load staticfiles %} +{% block content %} + + + + + + + + + + + +
    +
    CERT +
    +
    +

    The CERT Division

    +

    The CERT Division is a leader in cybersecurity. We partner with government, industry, law enforcement, and academia to improve the security and resilience of computer systems and networks. We study problems that have widespread cybersecurity implications and develop advanced methods and tools to counter large-scale, sophisticated cyber threats.

    +
    +
    + +
    +
    +

    CMU Foundation Theme Usage

    +
    +
    + +
    +
    +
    +

    Here are some examples of the modules contained in the CSS

    +

    (Here is a Lead Paragraph) The Vulnerability Notes Database contains two types of documents: Vulnerability Notes that describe vulnerabilities that may affect one or more vendors, and Vendor Information documents (also called vendor records), that provide vendor-specific information (e.g., solutions, workarounds, references, and status) about a vulnerability. The fields in each of these documents are described below in more detail.

    +

    (A Block Quote) Vulnerability Notes that describe vulnerabilities that may affect one or more vendors, and Vendor Information documents (also called vendor records), that provide vendor-specific information (e.g., solutions, workarounds, references, and status) about a vulnerability. The fields in each of these documents are described below in more detail.

    +
    +
    +

    Foundation Documentation
    Everything you need to know about using the framework.

    +
    +
    +

    Foundation Code Skills
    These online courses offer you a chance to better understand how Foundation works and how you can master it to create awesome projects.

    +
    +
    +

    Foundation Forum
    Join the Foundation community to ask a question or show off your knowledge.

    +
    +
    +
    +
    +

    Foundation on Github
    Latest code, issue reports, feature requests and more.

    +
    +
    +

    @zurbfoundation
    Ping us on Twitter if you have questions. When you build something with this we'd love to see it (and send you a totally boss sticker).

    +
    +
    +
    +
    +
    + +
    +
    +
    Here’s your basic grid:
    + + +
    +
    +
    +

    This is a twelve column section in a row. Each of these includes a div.callout element so you can see where the columns are - it's not required at all for the grid.

    +
    +
    +
    +
    +
    +
    +

    Six columns

    +
    +
    +
    +
    +

    Six columns

    +
    +
    +
    +
    +
    +
    +

    Four columns

    +
    +
    +
    +
    +

    Four columns

    +
    +
    +
    +
    +

    Four columns

    +
    +
    +
    + +
    + +
    We bet you’ll need a form somewhere:
    +
    +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + +
    + + .com +
    +
    +
    +
    +
    +
    + + +
    +
    +
    +
    + + + +
    +
    + + + +
    +
    +
    +
    + + +
    +
    +
    +
    + +
    +
    Try one of these buttons:
    +

    Simple Button
    + Inverted Button
    + + Success Btn
    + Warning Btn
    + Danger Btn

    +
    +
    So many components, girl!
    +

    A whole kitchen sink of goodies comes with Foundation. Check out the docs to see them all, along with details on making them your own.

    + Go to Foundation Docs +
    +
    +
    + + + + + + + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/vincepub/templates/vincepub/index.html b/vincepub/templates/vincepub/index.html new file mode 100644 index 0000000..c0731db --- /dev/null +++ b/vincepub/templates/vincepub/index.html @@ -0,0 +1,87 @@ +{% extends "vincepub/base.html" %} +{% load staticfiles %} +{% block content %} +
    +
    + +
    +
    +

    Vulnerability Notes Database

    +

    The Vulnerability Notes Database provides information about software vulnerabilities. Vulnerability notes include summaries, technical details, remediation information, and lists of affected vendors. Most vulnerability notes are the result of private coordination and disclosure efforts. For more comprehensive coverage of public vulnerability reports, consider the National Vulnerability Database (NVD). CERT/CC also publishes the Vulnerability Notes Data Archive on GitHub.

    +
    +
    +
    +
    +

    Recently Published Vulnerabilities

    +
    + {% for note in pub_list %} +
    +
    + {% with '/vuls/id/'|add:note.idnumber as vul_link %} +

    + + {{ note.vuid }}: {{ note.name }} + {% endwith %} + +

    +
    {{ note.datefirstpublished|date:"F d, Y" }}
    +
    +
    + {% endfor %} +
    + +
    +
    + +
    +
    + +
    +
    +
    +

    Want to report a vulnerability?

    +

    The CERT Coordination Center (CERT/CC) prioritizes coordination efforts on vulnerabilities that affect multiple vendors or that impact safety, critical or internet infrastructure, or national security. We also prioritize reports that affect sectors that are new to vulnerability disclosure. We may be able to provide assistance for reports when the coordination process breaks down.

    +

    Before reporting a vulnerability to us, we recommend reading our vulnerability disclosure policy and guidance. +

    Report a Vulnerability

    +
    +
    +
    +
    +
    + + + +{% endblock %} diff --git a/vincepub/templates/vincepub/report.html b/vincepub/templates/vincepub/report.html new file mode 100644 index 0000000..efcbfcb --- /dev/null +++ b/vincepub/templates/vincepub/report.html @@ -0,0 +1,50 @@ +{% extends VINCEPUB_BASE_TEMPLATE %} +{% load staticfiles %} +{% block js %} +{{ block.super }} +{% endblock %} +{% block content %} +
    + +
    +
    +
    +
    +
    +

    Report a Vulnerability

    +
    +
    +
    + + +

    Before reporting any vulnerabilities to the CERT Coordination Center (CERT/CC) and making them public, try contacting the vendor directly. Some vendors offer bug bounty programs.

    +

    We recommend reading our vulnerability disclosure policy and guidance before submitting a vulnerability report. We send information provided in vulnerability reports to affected vendors.

    + +

    CERT/CC does not accept or respond to every report. We prioritize reports that affect multiple vendors or that impact safety, critical or internet infrastructure, or national security. We also prioritize reports that affect sectors that are new to vulnerability disclosure. We may be able to provide assistance for reports when the coordination process breaks down.

    + Begin a Report + +
    +

     

    +
     
    +
    + +
    + +{% endblock %} + diff --git a/vincepub/templates/vincepub/vince.html b/vincepub/templates/vincepub/vince.html index 7a08f6e..064f32a 100644 --- a/vincepub/templates/vincepub/vince.html +++ b/vincepub/templates/vincepub/vince.html @@ -6,7 +6,7 @@

    VINCE

    -

    Welcome to the Vulnerability Information and Coordination Environment (VINCE). If you are a vendor and would like to communicate with us about a vulnerability or update your contact information, please create an account or sign in. You can also report a vulnerability to us, with or without a VINCE account. +

    Welcome to the Vulnerability Information and Coordination Environment (VINCE). If you are a vendor and would like to communicate with us about a vulnerability or update your contact information, please create an account or sign in. You can also report a vulnerability to us, with or without a VINCE account. For more information see the VINCE Documentation site

    Create an Account diff --git a/vinny/models.py b/vinny/models.py index 30ca15d..e034000 100644 --- a/vinny/models.py +++ b/vinny/models.py @@ -236,12 +236,12 @@ def _get_modified(self): modified = property(_get_modified) - #Security issue remove mass unpickling + # Security issue remove mass unpickling def _set_settings(self, data): # data should always be a Python dictionary. sdata = {} - if not isinstance(data,dict): + if not isinstance(data, dict): logger.warn("Non dictionary item sent to settings %s" % str(data)) try: sdata = json.dumps(data) @@ -254,12 +254,12 @@ def _get_settings(self): if self.settings_pickled: try: data = json.loads(self.settings_pickled) - if isinstance(data,dict): + if isinstance(data, dict): return data else: logger.warn("Non dictionary item sent to settings %s" % str(data)) except Exception as e: - logger.warn("Generic error when trying to json parse data %s " %(str(e))) + logger.warn("Generic error when trying to json parse data %s " % (str(e))) return {} settings = property(_get_settings, _set_settings) @@ -2112,6 +2112,14 @@ class CaseViewed(models.Model): date_viewed = models.DateTimeField(default=timezone.now) +class APIAccess(models.Model): + url = models.URLField() + + user = models.ForeignKey(settings.AUTH_USER_MODEL, blank=True, null=True, on_delete=models.CASCADE) + + date_viewed = models.DateTimeField(default=timezone.now) + + class CaseCoordinator(models.Model): case = models.ForeignKey(Case, on_delete=models.CASCADE) @@ -2189,7 +2197,7 @@ class Status(models.IntegerChoices): # Admin user or whoever rejected/approved this request. completed_by = models.CharField(max_length=255, blank=True, null=True) - expires_at = models.DateTimeField(default=timezone.now() + timedelta(days=30)) + expires_at = models.DateTimeField(blank=True, null=True) contact = models.ForeignKey(VinceCommContact, on_delete=models.CASCADE) thread = models.ForeignKey(Thread, on_delete=models.DO_NOTHING, blank=True, null=True) @@ -2199,3 +2207,8 @@ class Meta: def __str__(self): return f"User approve request for {self.user} to join {self.contact}" + + def save(self, *args, **kwargs): + if not self.pk: + self.expires_at = timezone.now() + timedelta(days=30) + super(UserApproveRequest, self).save(*args, **kwargs) diff --git a/vinny/templates/vinny/base.html b/vinny/templates/vinny/base.html index 6c110e9..120e7e2 100644 --- a/vinny/templates/vinny/base.html +++ b/vinny/templates/vinny/base.html @@ -147,7 +147,7 @@

    diff --git a/vinny/urls.py b/vinny/urls.py index 9043c17..a96f40e 100644 --- a/vinny/urls.py +++ b/vinny/urls.py @@ -35,131 +35,179 @@ from django.views.generic import TemplateView, RedirectView urlpatterns = [ - re_path('^$', RedirectView.as_view(pattern_name="vinny:dashboard"), name='index'), - #path('login/', RedirectView.as_view(pattern_name='cogauth:login'), name="login"), -# path('logout/', auth_views.LogoutView.as_view(template_name='vince/logout.html'), name='logout'), - path('signup/', cogauth_views.RegisterView.as_view(), name='signup'), - path('pending/', TemplateView.as_view(template_name='vinny/pending.html'), name='pending'), - path('service/', TemplateView.as_view(template_name='vinny/service.html'), name='serviceaccount'), - path('login/', cogauth_views.COGLoginView.as_view(), name='login'), - path('dashboard/', views.DashboardView.as_view(), name='dashboard'), - path('viewall/', views.LimitedAccessView.as_view(), name='limited'), - path('limited/report', views.VinceCommReportView.as_view(), name='vcreport'), - re_path(r'^vrf/attachments/(?P\d+)/$', views.VinceVRFAttachmentView.as_view(), name='vrf_attachment'), - re_path(r'attachments/(?P(case|msg|report|track))/(?P.*)$', views.VinceAttachmentView.as_view(), name='attachment'), - re_path('limited/reports/print/(?P[0-9]+)/(?P[0-9]+)/$', views.VinceCommPrintReportsView.as_view(), name='printreport'), - re_path('^case/summary/(?P\d+)/$', views.CaseSummaryView.as_view(), name='case_summary'), - re_path('^case/request/(?P\d+)/$', views.RequestAccessView.as_view(), name='requestaccess'), - path('preferences/', views.PreferencesView.as_view(), name='preferences'), - path('dashboard/filter/', views.DashboardCaseView.as_view(), name='dashboardfilter'), - path('limited/filter/', views.LimitedAccessSearch.as_view(), name='limitedfilter'), - path('reports/filter/', views.MyReportsFilterView.as_view(), name='myreportsfilter'), - path('contact/', views.ContactView.as_view(), name='contact'), - path('profile/newcolor/', views.GenerateNewRandomColor.as_view(), name='newcolor'), - re_path('^contact/(?P\d+)/$', views.ContactView.as_view(), name='contact'), - path('contact/multi/', views.MultipleContactView.as_view(), name='multiple_contacts'), - re_path('^vc/contact/edit/(?P\d+)/$', views.EditContactView.as_view(), name='editcontact'), - re_path('^contact/(?P\d+)/add/logo/$', views.ContactAddLogoView.as_view(), name='addlogo'), - re_path('^groupadmin/change/access/(?P\d+)/', views.ChangeDefaultCaseAccess.as_view(), name='changeaccess'), - re_path('^groupadmin/caseaccess/(?P\d+)/(?P\d+)/', views.CaseAccessView.as_view(), name='caseaccess'), - path('groupadmin/', views.AdminView.as_view(), name='admin'), - path('groupadmin/multi/', views.MultipleGroupAdminView.as_view(), name='multiple_admins'), - re_path('^groupadmin/(?P\d+)/promote/(?P\d+)/$', views.PromoteUserView.as_view(), name='promoteuser'), - re_path('^groupadmin/(?P\d+)/$', views.AdminView.as_view(), name='admin'), - re_path('^groupadmin/users/(?P\d+)/$', views.UserCaseAccessView.as_view(), name='adminusers'), - re_path('^groupadmin/service/create/(?P\d+)/$', views.CreateServiceAccountView.as_view(), name='createservice'), - re_path('groupadmin/(?P\d+)/adduser/', views.AdminAddUserView.as_view(), name='adduser'), - re_path('^groupadmin/(?P\d+)/rmuser/(?P(contact|user))/(?P\d+)/$', views.AdminRemoveUser.as_view(), name='rmuser'), - re_path('^groupadmin/(?P\d+)/email/modify/(?P(email|user))/(?P\d+)/$', views.ModifyEmailNotifications.as_view(), name='changeemail'), - path('inbox/', views.InboxView.as_view(), name='inbox'), - re_path('^inbox/(?P(sent))/$', views.InboxView.as_view(), name='inbox'), - path('inbox/filter/', views.SearchThreadsView.as_view(), name='filterthreads'), - re_path('^thread/(?P\d+)/$', views.ThreadView.as_view(), name='thread_detail'), - re_path('^thread/msg/(?P\d+)/$', views.MessageView.as_view(), name='msg_detail'), - re_path('^thread/messages/(?P\d+)/$', views.MessagesView.as_view(), name='messages'), - re_path('^thread/(?P\d+)/delete/$', views.ThreadDeleteView.as_view(), name='thread_delete'), - re_path('^sendmsg/(?P[1-9]|10)?/$', views.SendMessageView.as_view(), name='sendmsg'), - re_path('^sendmsg/(?P[2])/(?P\d+)/$', views.SendMessageView.as_view(), name='sendmsg'), - path('sendmsg/all/', views.SendMessageAllView.as_view(), name='sendmsgall'), - path('auto/api/allvendors/', views.autocomplete_allvendors, name='all_vendors'), - path('auto/api/vlookup/', views.VendorLookupView.as_view(), name='vendorlookup'), - path('auto/api/vendors/', views.autocomplete_vendor, name='auto_vendor'), - path('auto/api/users/', views.autocomplete_users, name='auto_user'), - path('api/userapprove/', views.userapproverequest, {"caller": "vinny"},name='userapprove'), - re_path('^auto/api/coord/(?P\d+)/$', views.autocomplete_coordinators, name='auto_coord'), - path('sendmsg/', views.SendMessageView.as_view(), name='sendmsg'), - path('sendmsg/user/', views.SendMessageUserView.as_view(), name='sendmsguser'), - re_path('^sendmsg/user/(?P\d+)/$', views.SendMessageUserView.as_view(), name='sendmsgus'), - re_path('^sendmsg/group/(?P\d+)_(?P\d+)?/$', views.SendMessageUserView.as_view(), name='sendmsggroup'), - re_path('^sendmsg/admins/(?P\d+)_(?P\d+)?/$', views.SendMessageUserView.as_view(), name='sendmsgadmins'), - #path('groupchat/', views.GroupChatView.as_view(), name='groupchat'), - re_path('^groupchat/case/(?P\d+)/$', views.GroupChatView.as_view(), name='groupchatcase'), - #path('redirect/vintrack/', views.RedirectVince.as_view(), name='redirect_vince'), - re_path('^case/(?P[0-9]+)?/$', views.CaseView.as_view(), name='case'), - re_path('^case/(?P[0-9]+)?/vv/(?P[0-9]+)?/$', views.CaseView.as_view(), name='vendorcase'), - re_path('^case/vin/(?P[0-9]+)?/$', views.VinceCaseView.as_view(), name='vincase'), - re_path('^case/post/(?P[0-9]+)?/$', views.PostCaseView.as_view(), name='postcase'), - re_path('^post/(?P[0-9]+)/$', views.PostView.as_view(), name='post'), - #re_path('^post/(?P[0-9]+)/(?P[0-9]+)/$', views.ThreadedPostView.as_view(), name='replies'), - re_path('^post/diff/(?P[0-9]+)/$', views.PostDiffView.as_view(), name='diff'), - re_path('^post/edit/(?P[0-9]+)/$', views.EditPostView.as_view(), name='editpost'), - re_path('^post/edit/confirm/(?P[0-9]+)/$', views.DeletePostView.as_view(), name='rmpost'), - re_path('^case/(?P[0-9]+)?/status/$', views.ViewStatusView.as_view(), name='status'), - re_path('^case/(?P[0-9]+)?/vendors/all/$', views.LoadVendorsView.as_view(), name='loadvendors'), - re_path('^case/(?P[0-9]+)?/vendors/json/$', views.JsonVendorsView.as_view(), name='loadjson'), - re_path('^case/(?P[0-9]+)/(?P[0-9]+)/status/$', views.ViewStatusView.as_view(), name='status'), - re_path('^case/(?P[0-9]+)/multiple/$', views.MultipleStatusView.as_view(), name='multiple_status'), - re_path('^case/(?P[0-9]+)?/mute/$', views.MuteCaseView.as_view(), name='mute'), - re_path('^case/(?P[0-9]+)/add/document/$', views.CaseDocumentCreateView.as_view(), name='addfile'), - re_path('^case/(?P[0-9]+)/rm/(?P[0-9]+)/document/$', views.RemoveFileView.as_view(), name='rmfile'), - re_path('^case/tracking/$', views.casetracking, name='casetracking'), - re_path('^case/(?P[0-9]+)/status/update/$', views.UpdateStatusView.as_view(), name='update_status'), - re_path('^case/(?P[0-9]+)/report/$', views.CaseRequestView.as_view(), name='cr'), - re_path('^case/(?P[0-9]+)/vuls/$', views.VulnerabilityDetailView.as_view(), name='vuls'), - re_path('^case/vulns/(?P[0-9]+)/$', views.SingleVulDetailView.as_view(), name='vuldetail'), - re_path('^case/(?P[0-9]+)/notedraft/$', views.VulNoteView.as_view(), name='vulnote'), - re_path('^vul/(?P[0-9]+)/providestmt/$', views.AddStatement.as_view(), name='providestmt'), - re_path('^vul/(?P[0-9]+)/providestmt/(?P[0-9]+)/$', views.AddStatement.as_view(), name='providestmt'), - re_path('^case_search/$', views.CaseFilter.as_view(), name='casesearch'), - re_path('^case/(?P[0-9]+)/member/(?P[0-9]+)/$', views.GetStatementView.as_view(), name='statement'), - path('case/results/', views.CaseFilterResults.as_view(), name='caseresults'), - path('construction/', views.UnderConstruction.as_view(), name='construction'), - re_path('^cr/(?P[0-9]+)/$', views.CRView.as_view(), name='cr_report'), - path('report/', views.ReportView.as_view(), name='report'), - re_path('^report/(?P[0-9]+)?/add/file/$', views.ReportDocumentCreateView.as_view(), name='addreportfile'), - re_path('^report/(?P[0-9]+)?/update/$', views.UpdateReportView.as_view(), name='reportupdate'), - re_path('profile/user_card/(?P[0-9a-fA-F]+)?/$', views.UserCardView.as_view(), name='usercard'), - re_path('profile/group_card/(?P[0-9a-fA-F]+)?/$', views.GroupCardView.as_view(), name='groupcard'), - #note: var "vinny:groupcardcase" is not used in templates anymore + re_path("^$", RedirectView.as_view(pattern_name="vinny:dashboard"), name="index"), + # path('login/', RedirectView.as_view(pattern_name='cogauth:login'), name="login"), + # path('logout/', auth_views.LogoutView.as_view(template_name='vince/logout.html'), name='logout'), + path("signup/", cogauth_views.RegisterView.as_view(), name="signup"), + path("pending/", TemplateView.as_view(template_name="vinny/pending.html"), name="pending"), + path("service/", TemplateView.as_view(template_name="vinny/service.html"), name="serviceaccount"), + path("login/", cogauth_views.COGLoginView.as_view(), name="login"), + path("dashboard/", views.DashboardView.as_view(), name="dashboard"), + path("viewall/", views.LimitedAccessView.as_view(), name="limited"), + path("limited/report", views.VinceCommReportView.as_view(), name="vcreport"), + re_path(r"^vrf/attachments/(?P\d+)/$", views.VinceVRFAttachmentView.as_view(), name="vrf_attachment"), + re_path( + r"attachments/(?P(case|msg|report|track))/(?P.*)$", + views.VinceAttachmentView.as_view(), + name="attachment", + ), + re_path( + "limited/reports/print/(?P[0-9]+)/(?P[0-9]+)/$", + views.VinceCommPrintReportsView.as_view(), + name="printreport", + ), + re_path("^case/summary/(?P\d+)/$", views.CaseSummaryView.as_view(), name="case_summary"), + re_path("^case/request/(?P\d+)/$", views.RequestAccessView.as_view(), name="requestaccess"), + path("preferences/", views.PreferencesView.as_view(), name="preferences"), + path("dashboard/filter/", views.DashboardCaseView.as_view(), name="dashboardfilter"), + path("limited/filter/", views.LimitedAccessSearch.as_view(), name="limitedfilter"), + path("reports/filter/", views.MyReportsFilterView.as_view(), name="myreportsfilter"), + path("contact/", views.ContactView.as_view(), name="contact"), + path("profile/newcolor/", views.GenerateNewRandomColor.as_view(), name="newcolor"), + re_path("^contact/(?P\d+)/$", views.ContactView.as_view(), name="contact"), + path("contact/multi/", views.MultipleContactView.as_view(), name="multiple_contacts"), + re_path("^vc/contact/edit/(?P\d+)/$", views.EditContactView.as_view(), name="editcontact"), + re_path("^contact/(?P\d+)/add/logo/$", views.ContactAddLogoView.as_view(), name="addlogo"), + re_path( + "^groupadmin/change/access/(?P\d+)/", views.ChangeDefaultCaseAccess.as_view(), name="changeaccess" + ), + re_path( + "^groupadmin/caseaccess/(?P\d+)/(?P\d+)/", + views.CaseAccessView.as_view(), + name="caseaccess", + ), + path("groupadmin/", views.AdminView.as_view(), name="admin"), + path("groupadmin/multi/", views.MultipleGroupAdminView.as_view(), name="multiple_admins"), + re_path( + "^groupadmin/(?P\d+)/promote/(?P\d+)/$", views.PromoteUserView.as_view(), name="promoteuser" + ), + re_path("^groupadmin/(?P\d+)/$", views.AdminView.as_view(), name="admin"), + re_path("^groupadmin/users/(?P\d+)/$", views.UserCaseAccessView.as_view(), name="adminusers"), + re_path( + "^groupadmin/service/create/(?P\d+)/$", + views.CreateServiceAccountView.as_view(), + name="createservice", + ), + re_path("groupadmin/(?P\d+)/adduser/", views.AdminAddUserView.as_view(), name="adduser"), + re_path( + "^groupadmin/(?P\d+)/rmuser/(?P(contact|user))/(?P\d+)/$", + views.AdminRemoveUser.as_view(), + name="rmuser", + ), + re_path( + "^groupadmin/(?P\d+)/email/modify/(?P(email|user))/(?P\d+)/$", + views.ModifyEmailNotifications.as_view(), + name="changeemail", + ), + path("inbox/", views.InboxView.as_view(), name="inbox"), + re_path("^inbox/(?P(sent))/$", views.InboxView.as_view(), name="inbox"), + path("inbox/filter/", views.SearchThreadsView.as_view(), name="filterthreads"), + re_path("^thread/(?P\d+)/$", views.ThreadView.as_view(), name="thread_detail"), + re_path("^thread/msg/(?P\d+)/$", views.MessageView.as_view(), name="msg_detail"), + re_path("^thread/messages/(?P\d+)/$", views.MessagesView.as_view(), name="messages"), + re_path("^thread/(?P\d+)/delete/$", views.ThreadDeleteView.as_view(), name="thread_delete"), + re_path("^sendmsg/(?P[1-9]|10)?/$", views.SendMessageView.as_view(), name="sendmsg"), + re_path("^sendmsg/(?P[2])/(?P\d+)/$", views.SendMessageView.as_view(), name="sendmsg"), + path("sendmsg/all/", views.SendMessageAllView.as_view(), name="sendmsgall"), + path("auto/api/allvendors/", views.autocomplete_allvendors, name="all_vendors"), + path("auto/api/vlookup/", views.VendorLookupView.as_view(), name="vendorlookup"), + path("auto/api/vendors/", views.autocomplete_vendor, name="auto_vendor"), + path("auto/api/users/", views.autocomplete_users, name="auto_user"), + path("api/userapprove/", views.userapproverequest, {"caller": "vinny"}, name="userapprove"), + re_path("^auto/api/coord/(?P\d+)/$", views.autocomplete_coordinators, name="auto_coord"), + path("sendmsg/", views.SendMessageView.as_view(), name="sendmsg"), + path("sendmsg/user/", views.SendMessageUserView.as_view(), name="sendmsguser"), + re_path("^sendmsg/user/(?P\d+)/$", views.SendMessageUserView.as_view(), name="sendmsgus"), + re_path( + "^sendmsg/group/(?P\d+)_(?P\d+)?/$", views.SendMessageUserView.as_view(), name="sendmsggroup" + ), + re_path( + "^sendmsg/admins/(?P\d+)_(?P\d+)?/$", views.SendMessageUserView.as_view(), name="sendmsgadmins" + ), + # path('groupchat/', views.GroupChatView.as_view(), name='groupchat'), + re_path("^groupchat/case/(?P\d+)/$", views.GroupChatView.as_view(), name="groupchatcase"), + # path('redirect/vintrack/', views.RedirectVince.as_view(), name='redirect_vince'), + re_path("^case/(?P[0-9]+)?/$", views.CaseView.as_view(), name="case"), + re_path("^case/(?P[0-9]+)?/vv/(?P[0-9]+)?/$", views.CaseView.as_view(), name="vendorcase"), + re_path("^case/vin/(?P[0-9]+)?/$", views.VinceCaseView.as_view(), name="vincase"), + re_path("^case/post/(?P[0-9]+)?/$", views.PostCaseView.as_view(), name="postcase"), + re_path("^post/(?P[0-9]+)/$", views.PostView.as_view(), name="post"), + # re_path('^post/(?P[0-9]+)/(?P[0-9]+)/$', views.ThreadedPostView.as_view(), name='replies'), + re_path("^post/diff/(?P[0-9]+)/$", views.PostDiffView.as_view(), name="diff"), + re_path("^post/edit/(?P[0-9]+)/$", views.EditPostView.as_view(), name="editpost"), + re_path("^post/edit/confirm/(?P[0-9]+)/$", views.DeletePostView.as_view(), name="rmpost"), + re_path("^case/(?P[0-9]+)?/status/$", views.ViewStatusView.as_view(), name="status"), + re_path("^case/(?P[0-9]+)?/vendors/all/$", views.LoadVendorsView.as_view(), name="loadvendors"), + re_path("^case/(?P[0-9]+)?/vendors/json/$", views.JsonVendorsView.as_view(), name="loadjson"), + re_path("^case/(?P[0-9]+)/(?P[0-9]+)/status/$", views.ViewStatusView.as_view(), name="status"), + re_path("^case/(?P[0-9]+)/multiple/$", views.MultipleStatusView.as_view(), name="multiple_status"), + re_path("^case/(?P[0-9]+)?/mute/$", views.MuteCaseView.as_view(), name="mute"), + re_path("^case/(?P[0-9]+)/add/document/$", views.CaseDocumentCreateView.as_view(), name="addfile"), + re_path("^case/(?P[0-9]+)/rm/(?P[0-9]+)/document/$", views.RemoveFileView.as_view(), name="rmfile"), + re_path("^case/tracking/$", views.casetracking, name="casetracking"), + re_path("^case/(?P[0-9]+)/status/update/$", views.UpdateStatusView.as_view(), name="update_status"), + re_path("^case/(?P[0-9]+)/report/$", views.CaseRequestView.as_view(), name="cr"), + re_path("^case/(?P[0-9]+)/vuls/$", views.VulnerabilityDetailView.as_view(), name="vuls"), + re_path("^case/vulns/(?P[0-9]+)/$", views.SingleVulDetailView.as_view(), name="vuldetail"), + re_path("^case/(?P[0-9]+)/notedraft/$", views.VulNoteView.as_view(), name="vulnote"), + re_path("^vul/(?P[0-9]+)/providestmt/$", views.AddStatement.as_view(), name="providestmt"), + re_path( + "^vul/(?P[0-9]+)/providestmt/(?P[0-9]+)/$", views.AddStatement.as_view(), name="providestmt" + ), + re_path("^case_search/$", views.CaseFilter.as_view(), name="casesearch"), + re_path("^case/(?P[0-9]+)/member/(?P[0-9]+)/$", views.GetStatementView.as_view(), name="statement"), + path("case/results/", views.CaseFilterResults.as_view(), name="caseresults"), + path("construction/", views.UnderConstruction.as_view(), name="construction"), + re_path("^cr/(?P[0-9]+)/$", views.CRView.as_view(), name="cr_report"), + path("report/", views.ReportView.as_view(), name="report"), + re_path("^report/(?P[0-9]+)?/add/file/$", views.ReportDocumentCreateView.as_view(), name="addreportfile"), + re_path("^report/(?P[0-9]+)?/update/$", views.UpdateReportView.as_view(), name="reportupdate"), + re_path("profile/user_card/(?P[0-9a-fA-F]+)?/$", views.UserCardView.as_view(), name="usercard"), + re_path("profile/group_card/(?P[0-9a-fA-F]+)?/$", views.GroupCardView.as_view(), name="groupcard"), + # note: var "vinny:groupcardcase" is not used in templates anymore # use {{groupcontact.url}}{{case.id}} to build groupcardcase URLs - re_path('profile/group_card/(?P[0-9a-fA-F]+)?/(?P[0-9]+)?/$', views.GroupCardView.as_view(), name='groupcardcase'), - path('reports/pub/', views.AdminReportsView.as_view(), name='adminreports'), - path('reports/', views.ReportsView.as_view(), name='reports'), - path('api/vendor/', views.VendorInfoAPIView.as_view(), name='vendor_api'), - path('api/cases/', views.CasesAPIView.as_view(), name='cases_api'), - re_path('api/case/(?P\d+)/$', views.CaseAPIView.as_view({'get':'retrieve'}), name='case_api'), - re_path('api/case/posts/(?P\d+)/$', views.CasePostAPIView.as_view(), name='case_post_api'), - re_path('api/case/(?P\d+)/posts/$', views.CasePostAPIView.as_view(), name='case_post_api'), - re_path('api/case/report/(?P\d+)/$', views.CaseReportAPIView.as_view(), name='case_report_api'), - re_path('api/case/(?P\d+)/report/$', views.CaseReportAPIView.as_view(), name='case_report_api'), - re_path('api/case/vuls/(?P\d+)/$', views.CaseVulAPIView.as_view(), name='case_vul_api'), - re_path('api/case/(?P\d+)/vuls/$', views.CaseVulAPIView.as_view(), name='case_vul_api'), - re_path('api/case/vendor/statement/(?P\d+)/$', views.UpdateVendorStatusAPIView.as_view(), name='update_stmt_api'), - re_path('api/case/(?P\d+)/vendor/statement/$', views.UpdateVendorStatusAPIView.as_view(), name='update_stmt_api'), - re_path('api/case/vendors/(?P\d+)/$', views.CaseVendorStatusAPIView.as_view(), name='case_vendor_api'), - re_path('api/case/(?P\d+)/vendors/$', views.CaseVendorStatusAPIView.as_view(), name='case_vendor_api'), - re_path('api/case/vendors/vuls/(?P\d+)/$', views.CaseVendorVulStatusAPIView.as_view(), name='case_vendor_vul_api'), - re_path('api/case/(?P\d+)/vendors/vuls/$', views.CaseVendorVulStatusAPIView.as_view(), name='case_vendor_vul_api'), - re_path('api/case/note/(?P\d+)/$', views.CaseVulNoteAPIView.as_view(), name='case_vulnote_api'), - re_path('api/case/(?P\d+)/note/$', views.CaseVulNoteAPIView.as_view(), name='case_vulnote_api'), - re_path('api/vuls/cve/(?P\d+)-(?P\d+)/$', views.CVEVulAPIView.as_view(), name='cve_lookup_api'), - re_path('api/case/(?P\d+)/csaf/$', views.CaseCSAFAPIView.as_view(), name='case_csaf_api'), - re_path('api/case/csaf/(?P\d+)/$', views.CaseCSAFAPIView.as_view(), name='case_csaf_api'), - re_path('api/unread_msg_count/$', views.UnreadCountAjax.as_view(), name='unread_msg_count'), + re_path( + "profile/group_card/(?P[0-9a-fA-F]+)?/(?P[0-9]+)?/$", + views.GroupCardView.as_view(), + name="groupcardcase", + ), + path("reports/pub/", views.AdminReportsView.as_view(), name="adminreports"), + path("reports/", views.ReportsView.as_view(), name="reports"), + path("api/vendor/", views.VendorInfoAPIView.as_view(), name="vendor_api"), + path("api/cases/", views.CasesAPIView.as_view(), name="cases_api"), + re_path("api/case/(?P\d+)/$", views.CaseAPIView.as_view({"get": "retrieve"}), name="case_api"), + re_path("api/case/posts/(?P\d+)/$", views.CasePostAPIView.as_view(), name="case_post_api"), + re_path("api/case/(?P\d+)/posts/$", views.CasePostAPIView.as_view(), name="case_post_api"), + re_path("api/case/report/(?P\d+)/$", views.CaseReportAPIView.as_view(), name="case_report_api"), + re_path("api/case/(?P\d+)/report/$", views.CaseReportAPIView.as_view(), name="case_report_api"), + re_path("api/case/vuls/(?P\d+)/$", views.CaseVulAPIView.as_view(), name="case_vul_api"), + re_path("api/case/(?P\d+)/vuls/$", views.CaseVulAPIView.as_view(), name="case_vul_api"), + re_path( + "api/case/vendor/statement/(?P\d+)/$", views.UpdateVendorStatusAPIView.as_view(), name="update_stmt_api" + ), + re_path( + "api/case/(?P\d+)/vendor/statement/$", views.UpdateVendorStatusAPIView.as_view(), name="update_stmt_api" + ), + re_path("api/case/vendors/(?P\d+)/$", views.CaseVendorStatusAPIView.as_view(), name="case_vendor_api"), + re_path("api/case/(?P\d+)/vendors/$", views.CaseVendorStatusAPIView.as_view(), name="case_vendor_api"), + re_path( + "api/case/vendors/vuls/(?P\d+)/$", views.CaseVendorVulStatusAPIView.as_view(), name="case_vendor_vul_api" + ), + re_path( + "api/case/(?P\d+)/vendors/vuls/$", views.CaseVendorVulStatusAPIView.as_view(), name="case_vendor_vul_api" + ), + re_path("api/case/note/(?P\d+)/$", views.CaseVulNoteAPIView.as_view(), name="case_vulnote_api"), + re_path("api/case/(?P\d+)/note/$", views.CaseVulNoteAPIView.as_view(), name="case_vulnote_api"), + re_path("api/vuls/cve/(?P\d+)-(?P\d+)/$", views.CVEVulAPIView.as_view(), name="cve_lookup_api"), + re_path("api/case/(?P\d+)/csaf/$", views.CaseCSAFAPIView.as_view(), name="case_csaf_api"), + re_path("api/case/csaf/(?P\d+)/$", views.CaseCSAFAPIView.as_view(), name="case_csaf_api"), + re_path("api/unread_msg_count/$", views.UnreadCountAjax.as_view(), name="unread_msg_count"), ] if settings.DEBUG: - urlpatterns.extend([ - path('tokens/', views.VinceTokens.as_view(), name='get_token'), - path('token/login/', views.TokenLogin.as_view(), name='tokenlogin'), - ]) + urlpatterns.extend( + [ + path("tokens/", views.VinceTokens.as_view(), name="get_token"), + path("token/login/", views.TokenLogin.as_view(), name="tokenlogin"), + ] + ) diff --git a/vinny/views.py b/vinny/views.py index 9b5c08a..18d6316 100644 --- a/vinny/views.py +++ b/vinny/views.py @@ -447,6 +447,17 @@ def object_to_json_response(obj, status=200): ) +def create_record_of_API_access(url, user): + now = timezone.now() + logger.debug(f"API access log: user {user} accessed {url} at {now}") + try: + api_access = APIAccess(url=url, user=user) + api_access.save() + except Exception as e: + logger.debug(f"error {e} while trying to create record of API access with endpoint {url} and user {user}") + return + + # A similar method exists for all VinceComm users exists # below autocomplete_allvendors() @login_required(login_url="vinny:login") @@ -4574,6 +4585,7 @@ def get_view_name(self): return "My Cases" def get_queryset(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) if is_in_group_vincetrack(self.request.user) or is_in_group_vincelimited(self.request.user): return Case.objects.all().order_by("-modified") @@ -4594,6 +4606,7 @@ def get_view_name(self): return f"Case Detail" def get_queryset(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) if is_in_group_vincelimited(self.request.user): return Case.objects.all().order_by("-modified") @@ -4609,6 +4622,7 @@ def get_view_name(self): return f"Posts for Case" def get_queryset(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) case = get_object_or_404(Case, vuid=self.kwargs["vuid"]) return Post.objects.filter(case=case, current_revision__isnull=False).order_by("-created") @@ -4622,6 +4636,7 @@ def get_view_name(self): return f"Original Report for Case" def get_object(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) case = get_object_or_404(Case, vuid=self.kwargs["vuid"]) return case.cr @@ -4634,6 +4649,7 @@ def get_view_name(self): return f"Case Vulnerabilities" def get_queryset(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) case = get_object_or_404(Case, vuid=self.kwargs["vuid"]) return CaseVulnerability.objects.filter(case=case, deleted=False) @@ -4646,6 +4662,7 @@ def get_view_name(self): return f"Vulnerability Specific Vendor Status for Case" def get_queryset(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) case = get_object_or_404(Case, vuid=self.kwargs["vuid"]) return CaseMemberStatus.objects.filter(member__case=case) @@ -4658,6 +4675,7 @@ def get_view_name(self): return f"Case Vendors" def get_queryset(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) case = get_object_or_404(Case, vuid=self.kwargs["vuid"]) return ( CaseMember.objects.filter(case=case, coordinator=False, reporter_group=False) @@ -4691,6 +4709,7 @@ def get(self, request, *args, **kwargs): # VINCE published vul vuln = VPVulSerializer(vul) vendors = VendorVulStatus.objects.filter(vul=vul) + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) if vendors: vv = VendorVulSerializer(vendors, many=True) return Response({"vulnerability": vuln.data, "note": report.data, "vendors": vv.data}) @@ -4710,6 +4729,7 @@ def get(self, request, *args, **kwargs): } # check for vendors vendors = VendorRecord.objects.filter(vuid=x.vuid) + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) if vendors: vv = NewVendorRecordSerializer(vendors, many=True) return Response({"vulnerability": vul, "note": report.data, "vendors": vv.data}) @@ -4724,6 +4744,7 @@ def get(self, request, *args, **kwargs): vuln = VulSerializer(vul) case = CaseSerializer(vul.case) vendors = CaseMemberStatus.objects.filter(vulnerability=vul) + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) if vendors: vv = VendorStatusSerializer(vendors, many=True) @@ -4926,6 +4947,7 @@ def get_view_name(self): def get_object(self): case = get_object_or_404(Case, vuid=self.kwargs["vuid"]) if case.note: + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) return case.note.vulnote @@ -4937,9 +4959,11 @@ def get_view_name(self): return f"Vendor Information" def get_queryset(self): + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) email = VinceCommEmail.objects.filter(email=self.request.user.email, status=True).values_list( "contact__id", flat=True ) + return VinceCommContact.objects.filter(id__in=email) @@ -5114,6 +5138,7 @@ def get_view_name(self): def get_object(self): svuid = self.kwargs["vuid"] + create_record_of_API_access(self.request.build_absolute_uri(), self.request.user) return Case.objects.filter(vuid=svuid).first() def handle_no_permission(self):