From a097590b62d2461bb6e5b7d155d692068df05c8f Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 14 Feb 2025 20:50:54 +0100 Subject: [PATCH 1/4] #10 Dodat je dummy za payment page, mailovi za payment i link za dokument --- backend/accounts/utils.py | 28 +++++++++++++++++++++++++++- backend/accounts/views.py | 4 ++-- backend/core/templates/payment.html | 11 +++++++++++ backend/core/urls.py | 2 ++ backend/core/views.py | 16 ++++++++++++++-- requirements.txt | 8 ++++++++ 6 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 backend/core/templates/payment.html diff --git a/backend/accounts/utils.py b/backend/accounts/utils.py index 6ed5ff0..a6bbefd 100644 --- a/backend/accounts/utils.py +++ b/backend/accounts/utils.py @@ -4,6 +4,8 @@ from .models import EmailConfirmation import uuid from django.conf import settings from django.utils.timezone import now +from backend.core.models import Document, Organization + def send_confirmation_email(email): @@ -21,4 +23,28 @@ def send_confirmation_email(email): message=f"Please click on the link to confirm your e-mail address: {confirmation_link}", from_email= settings.EMAIL_HOST_USER, recipient_list=[email] - ) \ No newline at end of file + ) + +def send_payment_email(email): + organization = Organization.objects.get(email=email) + document = Document.objects.get(organization=organization) + + payment_link = f"http://127.0.0.1:8000{reverse('core:payment_page')}?email={email}" + + send_mail( + subject="Complete your payment", + message=f"Click the link to proceed with payment: {payment_link}", + from_email=settings.EMAIL_HOST_USER, + recipient_list=[email], + fail_silently=False, + ) + +def send_document_email(email, document_link): + send_mail( + subject="Your Document is Ready", + message=f"You can access your document at any time here: {document_link}", + from_email=settings.EMAIL_HOST_USER, + recipient_list=[email], + fail_silently=False, + ) + diff --git a/backend/accounts/views.py b/backend/accounts/views.py index c644ca5..1757f37 100644 --- a/backend/accounts/views.py +++ b/backend/accounts/views.py @@ -4,7 +4,7 @@ from backend.accounts.forms import SignupForm from .models import EmailConfirmation from django.shortcuts import get_object_or_404, render from django.http import HttpResponse -from backend.accounts.utils import send_confirmation_email +from backend.accounts.utils import send_confirmation_email, send_payment_email from .tasks import create_document_for_organization class SignUpView(CreateView): @@ -20,7 +20,7 @@ def confirm_email(request, uuid): return render(request, 'confirmation_expired.html', {'email': confirmation.email}) task = create_document_for_organization.delay(confirmation.email) - print(f"Task ID: {task.id}") + send_payment_email(confirmation.email) return HttpResponse("Email is confirmed") diff --git a/backend/core/templates/payment.html b/backend/core/templates/payment.html new file mode 100644 index 0000000..22bd6c6 --- /dev/null +++ b/backend/core/templates/payment.html @@ -0,0 +1,11 @@ +{% extends 'base.html' %} + +{% block content %} +

Payment

+

Click the button below to pay and access your document.

+ +
+ {% csrf_token %} + +
+{% endblock %} \ No newline at end of file diff --git a/backend/core/urls.py b/backend/core/urls.py index 1b67d6c..9b5d2ea 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -11,4 +11,6 @@ urlpatterns = [ # url document/ recieves a parameter named 'uuid' and passes it to the view path('document//', v.document, name='document'), path('preview//', v.template_preview, name='template_preview'), + path("payment/", v.payment_page, name="payment_page"), + ] diff --git a/backend/core/views.py b/backend/core/views.py index 1eed1e7..19528da 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -5,7 +5,7 @@ from .forms import OrganizationForm from .models import Organization,Document,Risk, DocumentTemplate from backend.core.utils import get_top_risk from django.urls import reverse -from backend.accounts.utils import send_confirmation_email +from backend.accounts.utils import send_confirmation_email,send_document_email from django.contrib.admin.views.decorators import staff_member_required # @login_required @@ -56,4 +56,16 @@ def document(request, document_id): def template_preview(request, name): template = get_object_or_404(DocumentTemplate, name=name) parsed_template = template.to_dict() - return render(request, 'template_preview.html', {'template': parsed_template}) \ No newline at end of file + return render(request, 'template_preview.html', {'template': parsed_template}) + +def payment_page(request): + email = request.GET.get("email") + organization = Organization.objects.get(email=email) + document = Document.objects.get(organization=organization) + document_link = f"http://127.0.0.1:8000/document/{document.id}/" + + if request.method == "POST": + send_document_email(email, document_link) + return redirect(document_link) + + return render(request, "payment.html", {"email": email}) diff --git a/requirements.txt b/requirements.txt index de81b6c..7cadb9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,11 @@ Faker==33.0.0 isort==5.13.2 python-decouple==3.8 psycopg2-binary==2.9.10 +openai==1.63.0 +python-dotenv==1.0.1 +PyYAML==6.0.2 +celery==5.4.0 +django-celery-results==2.5.1 +redis==5.2.1 + + -- 2.47.3 From 5ed323140fe2827c5f4c19a8bfccbd49a7217042 Mon Sep 17 00:00:00 2001 From: Amir Date: Mon, 17 Feb 2025 20:36:59 +0100 Subject: [PATCH 2/4] #10 AI bira controle,dodate je dummy payment page --- backend/accounts/tasks.py | 73 +++++++----- .../accounts/confirmation_expired.html | 6 +- .../accounts/confirmation_success.html | 12 +- backend/accounts/utils.py | 2 - backend/accounts/views.py | 9 +- backend/core/templates/document_detail.html | 19 +++ backend/core/templates/payment.html | 17 +-- backend/core/urls.py | 2 +- backend/core/utils.py | 110 ++++++++++++------ backend/core/views.py | 32 ++++- start_services.sh | 12 ++ 11 files changed, 204 insertions(+), 90 deletions(-) create mode 100644 backend/core/templates/document_detail.html create mode 100755 start_services.sh diff --git a/backend/accounts/tasks.py b/backend/accounts/tasks.py index a75c1cb..7ed1aa0 100644 --- a/backend/accounts/tasks.py +++ b/backend/accounts/tasks.py @@ -1,52 +1,63 @@ from celery import shared_task from backend.core.models import Organization, Document, Risk, Control, DocumentRiskControl from backend.core.utils import get_top_risk, get_controls_for_risk -from django.shortcuts import get_object_or_404, render - +from django.shortcuts import get_object_or_404 +from django.template import Template, Context +import yaml +from backend.core.models import DocumentTemplate +from .utils import send_payment_email @shared_task def create_document_for_organization(confirmation_email): - organization = get_object_or_404(Organization, email=confirmation_email) top_risk_ids = get_top_risk(organization) top_risks = Risk.objects.filter(risk_id__in=top_risk_ids) - organization.risks.set(top_risks) - document = Document.objects.create(organization=organization) - document.add_segment('h1', "Top 10 Risks Identified") + template = DocumentTemplate.objects.first() + template_content = template.content - risk_content = "\n\n".join([ - f"Risk: {risk.risk_id} - {risk.risk_name} \n" - f"Category: {risk.category}\n" - f"Primary Impact: {risk.primary_impact} \n" - f"Secondary Impact: {risk.secondary_impact}\n" - f"Tertiary Impact: {risk.tretiary_impact} \n" - f"Detection Difficulty: {risk.detection_difficulty} \n" - f"Recovery Complexity: {risk.recovery_complexity} \n" - f"Business Impact Severity: {risk.businnes_impact_severity}\n" - for risk in top_risks - ]) - document.add_segment('body', f"Identified Risks: \n\n{risk_content}") - - controls_content = "Mitigation Controls:\n\n" - + risks_data = [] for risk in top_risks: - controls_content += f"Risk: {risk.risk_id} - {risk.risk_name}\n" + controls_data = [] + selected_controls = get_controls_for_risk(risk, organization) - selected_controls = get_controls_for_risk(risk) - for control_id, weight in selected_controls: - control = Control.objects.filter(id=control_id).first() + control = Control.objects.filter(id=control_id).first() if control: + document = Document.objects.create(organization=organization) DocumentRiskControl.objects.create( document=document, risk=risk, control=control, - weight=weight + weight=weight, ) - controls_content += f" - Control: {control.name} (Impact Weight: {weight}/10)\n" - - controls_content += "\n" - - document.add_segment('body', controls_content) \ No newline at end of file + + controls_data.append({ + 'name': control.name, + 'weight': weight, + }) + + risks_data.append({ + 'name': risk.risk_name, + 'description': risk.description, + 'controls': controls_data, + }) + + context = { + 'organization': organization, + 'risks': risks_data, + } + + django_template = Template(template_content) + rendered_content = django_template.render(Context(context)) + + document = Document.objects.create(organization=organization, content=rendered_content) + + document.add_segment('title', f"{organization.name} Risk Assessment Report", 1) + document.add_segment('introduction', f"This document outlines the risks and controls for {organization.name}.", 2) + document.add_segment('body', rendered_content, 3) + + send_payment_email(confirmation_email) + + return document diff --git a/backend/accounts/templates/accounts/confirmation_expired.html b/backend/accounts/templates/accounts/confirmation_expired.html index 7624b9a..8ca56b0 100644 --- a/backend/accounts/templates/accounts/confirmation_expired.html +++ b/backend/accounts/templates/accounts/confirmation_expired.html @@ -1,8 +1,10 @@ {% extends "base_login.html" %} - +{%block content%}

Link has expired!

{% csrf_token %} -
\ No newline at end of file + + +{% endblock %} \ No newline at end of file diff --git a/backend/accounts/templates/accounts/confirmation_success.html b/backend/accounts/templates/accounts/confirmation_success.html index 32a64d0..a26424f 100644 --- a/backend/accounts/templates/accounts/confirmation_success.html +++ b/backend/accounts/templates/accounts/confirmation_success.html @@ -1,5 +1,11 @@ -{% extends "base_login.html" %} +{% extends "base.html" %} +{%block content%} +
+
+

Email Confirmed!

+

Your email {{ email }} has been successfully verified.

+
+
-

Email Confirmed!

-

Your email {{ email }} has been successfully verified.

+{%endblock%} diff --git a/backend/accounts/utils.py b/backend/accounts/utils.py index a6bbefd..c9a6ba8 100644 --- a/backend/accounts/utils.py +++ b/backend/accounts/utils.py @@ -6,8 +6,6 @@ from django.conf import settings from django.utils.timezone import now from backend.core.models import Document, Organization - - def send_confirmation_email(email): confirmation, created = EmailConfirmation.objects.get_or_create(email=email) diff --git a/backend/accounts/views.py b/backend/accounts/views.py index 1757f37..45500d9 100644 --- a/backend/accounts/views.py +++ b/backend/accounts/views.py @@ -4,7 +4,7 @@ from backend.accounts.forms import SignupForm from .models import EmailConfirmation from django.shortcuts import get_object_or_404, render from django.http import HttpResponse -from backend.accounts.utils import send_confirmation_email, send_payment_email +from backend.accounts.utils import send_confirmation_email from .tasks import create_document_for_organization class SignUpView(CreateView): @@ -17,13 +17,10 @@ def confirm_email(request, uuid): confirmation = get_object_or_404(EmailConfirmation, uuid=uuid) if confirmation.is_expired(): - return render(request, 'confirmation_expired.html', {'email': confirmation.email}) + return render(request, 'accounts/confirmation_expired.html', {'email': confirmation.email}) task = create_document_for_organization.delay(confirmation.email) - send_payment_email(confirmation.email) - - - return HttpResponse("Email is confirmed") + return render(request, 'accounts/confirmation_success.html',{'email':confirmation.email}) def resend_confirmation(request,email): if request.method == 'POST': diff --git a/backend/core/templates/document_detail.html b/backend/core/templates/document_detail.html new file mode 100644 index 0000000..0c94d53 --- /dev/null +++ b/backend/core/templates/document_detail.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} + +{% block content %} +
+

Document Preview

+

Organization: {{ organization.name }}

+

{{ created_at }}

+ +

Identified Risks and Controls

+
    + {% for risk, controls in risks_with_controls.items %} +
  • +

    {{ risk.risk_name }}:

    +

    {{ controls }}

    +
  • + {% endfor %} +
+
+{% endblock %} diff --git a/backend/core/templates/payment.html b/backend/core/templates/payment.html index 22bd6c6..ab52439 100644 --- a/backend/core/templates/payment.html +++ b/backend/core/templates/payment.html @@ -1,11 +1,14 @@ {% extends 'base.html' %} {% block content %} -

Payment

-

Click the button below to pay and access your document.

- -
- {% csrf_token %} - -
+
+
+

Payment

+

Click the button below to pay and access your document.

+
+ {% csrf_token %} + +
+
+
{% endblock %} \ No newline at end of file diff --git a/backend/core/urls.py b/backend/core/urls.py index 9b5d2ea..146096f 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -12,5 +12,5 @@ urlpatterns = [ path('document//', v.document, name='document'), path('preview//', v.template_preview, name='template_preview'), path("payment/", v.payment_page, name="payment_page"), - + path('documentview//', v.docprew, name='generate_document_view'), ] diff --git a/backend/core/utils.py b/backend/core/utils.py index b261e10..8e67dad 100644 --- a/backend/core/utils.py +++ b/backend/core/utils.py @@ -1,7 +1,7 @@ from openai import OpenAI from django.conf import settings from .models import Risk, Control, Document, DocumentRiskControl -from django.shortcuts import get_object_or_404 +import time def extract_risk_factors(organization): excluded_fields={"name","email"} @@ -44,59 +44,101 @@ def get_top_risk(organization): """ response = client.chat.completions.create( - model="gpt-4", + model="gpt-4o-mini", messages=[{"role": "system", "content": prompt}] ) risk_ids = response.choices[0].message.content.strip().split(",") + print(f"Risks: {risk_ids}") return [int(risk_id) for risk_id in risk_ids if risk_id.isdigit()] -def get_controls_for_risk(risk): +def get_controls_for_risk(risk, organization): client = OpenAI(api_key=settings.OPENAI_API_KEY) all_controls = Control.objects.all() control_list = [] + risk_factors = extract_risk_factors(organization) + valid_control_ids = {control.id for control in all_controls} + for control in all_controls: control_list.append(f"Control ID: {control.id}, Control Name: {control.name}") prompt = f""" - You are a cyber security expert. For the risk '{risk.risk_name}', select 10 relevant controls - from the following list and assign a weight (1-10) based on how much they reduce risks. - Available Controls (only respond with control IDs and weights): + You are an expert in cybersecurity risk management. Given the risk "{risk.risk_name}" and its associated factors "{risk_factors}", + your task is to select **exactly 10 unique controls** from the provided list that best mitigate this risk. Each control should be assigned a weight between **1 and 10** based on its effectiveness in reducing the risk. + ### Rules: + 1. **Each control ID must be unique** (no duplicates). + 2. **Only return control IDs and weights** in the exact format below. + 3. **Weights must be between 1 and 10** (1 = low impact, 10 = high impact). + 4. **Do NOT add explanations, descriptions, or extra text.** + 5. **Ensure that control IDs are randomly distributed and diverse across different categories.** + ### Available Controls: {control_list} - Respond only with control IDs (numbers) and their corresponding weights (1-10). - Format: - ID: Weight: - Example: - 1: 9 - 2: 6 - 3: 4 + + ### Expected Response Format (STRICTLY FOLLOW THIS FORMAT): + ``` + : + : + + ``` + + ### Example Correct Response (NO DUPLICATES): + ``` + 12 : 8 + 45 : 7 + + ``` + + ⚠️ **If you provide duplicate control IDs, your response will be rejected. Ensure all control IDs are unique.** + ⚠️ **Follow the response format exactly. Any deviation will be considered invalid.** """ - response = client.chat.completions.create( - model="gpt-4", - messages=[{"role": "system", "content": prompt}] - ) + for attempt in range(5): + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "system", "content": prompt}] + ) + result = response.choices[0].message.content.strip() + print(f"AI Response (Attempt {attempt+1}):\n{result}") - result = response.choices[0].message.content.strip() - selected_controls = [] + selected_controls = [] + valid = True + control_ids_seen = set() - for line in result.split("\n"): - line = line.strip() + for line in result.split("\n"): + line = line.strip() + + parts = line.split(":") + if len(parts) == 2: + control_id_str = parts[0].replace("ID:", "").replace("id:", "").replace("Id:", "").strip() + weight_str = parts[1].strip().replace("Weight:", "").replace("weight:", "").strip() + + control_id_str = ''.join(filter(str.isdigit, control_id_str)) + weight_str = ''.join(filter(str.isdigit, weight_str)) + + if control_id_str and weight_str: + control_id = int(control_id_str) + weight = int(weight_str) + + if control_id in valid_control_ids and 1 <= weight <= 10: + if control_id in control_ids_seen: + valid = False + break + selected_controls.append((control_id, weight)) + control_ids_seen.add(control_id) + else: + valid = False + break + + if valid and len(selected_controls) == 10: + return selected_controls + + print("Invalid response or duplicate control IDs found, retrying...\n") + time.sleep(2) + + print("Failed to get a valid response after multiple attempts.") + return [] - parts = line.split("Weight:") - if len(parts) == 2: - control_id_str = parts[0].replace("ID:", "").replace("id:", "").replace("Id:", "").strip() - weight_str = parts[1].strip().replace("Weight:", "").replace("weight:","").strip() - control_id_str = ''.join(filter(str.isdigit, control_id_str)) - weight_str = ''.join(filter(str.isdigit, weight_str)) - control_id = int(control_id_str) - weight = int(weight_str) - print(f"ID: {control_id}, Weight: {weight}") - control = Control.objects.filter(id=control_id).first() - if control: - selected_controls.append((control_id, weight)) - return selected_controls[:10] diff --git a/backend/core/views.py b/backend/core/views.py index 19528da..08f46b5 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -1,13 +1,13 @@ import logging +import yaml from django.shortcuts import render, redirect , get_object_or_404 from .forms import OrganizationForm -from .models import Organization,Document,Risk, DocumentTemplate -from backend.core.utils import get_top_risk -from django.urls import reverse +from .models import Organization,Document,Risk, DocumentTemplate,DocumentRiskControl from backend.accounts.utils import send_confirmation_email,send_document_email from django.contrib.admin.views.decorators import staff_member_required + # @login_required # def index(request): # return HttpResponse('

Django

Página simples.

') @@ -26,7 +26,6 @@ def signup(request): if form.is_valid(): form.save() send_confirmation_email(form.data['email']) - return render(request, 'thankyou.html', { 'email': form.data['email'], }) @@ -69,3 +68,28 @@ def payment_page(request): return redirect(document_link) return render(request, "payment.html", {"email": email}) + + +def docprew(request, document_id): + doc = get_object_or_404(Document, id=document_id) + org = doc.organization + + document_risk_controls = DocumentRiskControl.objects.filter(document=doc) + + unique_risks = Risk.objects.filter(id__in=document_risk_controls.values('risk_id')).distinct() + + risks_with_controls = {} + + for risk in unique_risks: + related_controls = DocumentRiskControl.objects.filter(risk=risk) + + risk_controls = [control.control.name for control in related_controls] + + risks_with_controls[risk] = ", ".join(risk_controls) + + return render(request, 'document_detail.html', { + 'document': doc, + 'organization': org, + 'created_at': doc.created_at, + 'risks_with_controls': risks_with_controls, + }) \ No newline at end of file diff --git a/start_services.sh b/start_services.sh new file mode 100755 index 0000000..84d747e --- /dev/null +++ b/start_services.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +echo "Starting Django server..." +python3 manage.py runserver & + +echo "Starting Celery worker..." +celery -A backend worker --loglevel=info & + +echo "Starting Redis server on port 6380..." +redis-server --port 6380 & + +wait -- 2.47.3 From 3175f9e7cbe0b591388df5712d8edb0d7dbd2015 Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 21 Feb 2025 00:32:27 +0100 Subject: [PATCH 3/4] Resen konflikt --- backend/accounts/tasks.py | 73 ++++------ backend/accounts/views.py | 4 +- backend/core/templates/document_detail.html | 19 --- backend/core/urls.py | 1 - backend/core/utils.py | 152 ++++++++++++-------- backend/core/views.py | 33 +---- 6 files changed, 134 insertions(+), 148 deletions(-) delete mode 100644 backend/core/templates/document_detail.html diff --git a/backend/accounts/tasks.py b/backend/accounts/tasks.py index 7ed1aa0..56cf39a 100644 --- a/backend/accounts/tasks.py +++ b/backend/accounts/tasks.py @@ -1,63 +1,52 @@ from celery import shared_task from backend.core.models import Organization, Document, Risk, Control, DocumentRiskControl from backend.core.utils import get_top_risk, get_controls_for_risk -from django.shortcuts import get_object_or_404 -from django.template import Template, Context -import yaml -from backend.core.models import DocumentTemplate -from .utils import send_payment_email +from django.shortcuts import get_object_or_404, render + @shared_task def create_document_for_organization(confirmation_email): + organization = get_object_or_404(Organization, email=confirmation_email) top_risk_ids = get_top_risk(organization) top_risks = Risk.objects.filter(risk_id__in=top_risk_ids) + organization.risks.set(top_risks) - template = DocumentTemplate.objects.first() - template_content = template.content + document = Document.objects.create(organization=organization) + document.add_segment('h1', "Top 10 Risks Identified") - risks_data = [] + risk_content = "\n\n".join([ + f"Risk: {risk.risk_id} - {risk.risk_name} \n" + f"Category: {risk.category}\n" + f"Primary Impact: {risk.primary_impact} \n" + f"Secondary Impact: {risk.secondary_impact}\n" + f"Tertiary Impact: {risk.tretiary_impact} \n" + f"Detection Difficulty: {risk.detection_difficulty} \n" + f"Recovery Complexity: {risk.recovery_complexity} \n" + f"Business Impact Severity: {risk.businnes_impact_severity}\n" + for risk in top_risks + ]) + document.add_segment('body', f"Identified Risks: \n\n{risk_content}") + + controls_content = "Mitigation Controls:\n\n" + for risk in top_risks: - controls_data = [] - selected_controls = get_controls_for_risk(risk, organization) + controls_content += f"Risk: {risk.risk_id} - {risk.risk_name}\n" + selected_controls = get_controls_for_risk(risk ,organization=organization) + for control_id, weight in selected_controls: - control = Control.objects.filter(id=control_id).first() + control = Control.objects.filter(id=control_id).first() if control: - document = Document.objects.create(organization=organization) DocumentRiskControl.objects.create( document=document, risk=risk, control=control, - weight=weight, + weight=weight ) - - controls_data.append({ - 'name': control.name, - 'weight': weight, - }) - - risks_data.append({ - 'name': risk.risk_name, - 'description': risk.description, - 'controls': controls_data, - }) - - context = { - 'organization': organization, - 'risks': risks_data, - } - - django_template = Template(template_content) - rendered_content = django_template.render(Context(context)) - - document = Document.objects.create(organization=organization, content=rendered_content) - - document.add_segment('title', f"{organization.name} Risk Assessment Report", 1) - document.add_segment('introduction', f"This document outlines the risks and controls for {organization.name}.", 2) - document.add_segment('body', rendered_content, 3) - - send_payment_email(confirmation_email) - - return document + controls_content += f" - Control: {control.name} (Impact Weight: {weight}/10)\n" + + controls_content += "\n" + + document.add_segment('body', controls_content) \ No newline at end of file diff --git a/backend/accounts/views.py b/backend/accounts/views.py index 45500d9..3c75431 100644 --- a/backend/accounts/views.py +++ b/backend/accounts/views.py @@ -17,10 +17,10 @@ def confirm_email(request, uuid): confirmation = get_object_or_404(EmailConfirmation, uuid=uuid) if confirmation.is_expired(): - return render(request, 'accounts/confirmation_expired.html', {'email': confirmation.email}) + return render(request, 'confirmation_expired.html', {'email': confirmation.email}) task = create_document_for_organization.delay(confirmation.email) - return render(request, 'accounts/confirmation_success.html',{'email':confirmation.email}) + return render(request, 'confirmation_success.html', {'email': confirmation.email}) def resend_confirmation(request,email): if request.method == 'POST': diff --git a/backend/core/templates/document_detail.html b/backend/core/templates/document_detail.html deleted file mode 100644 index 0c94d53..0000000 --- a/backend/core/templates/document_detail.html +++ /dev/null @@ -1,19 +0,0 @@ -{% extends "base.html" %} - -{% block content %} -
-

Document Preview

-

Organization: {{ organization.name }}

-

{{ created_at }}

- -

Identified Risks and Controls

-
    - {% for risk, controls in risks_with_controls.items %} -
  • -

    {{ risk.risk_name }}:

    -

    {{ controls }}

    -
  • - {% endfor %} -
-
-{% endblock %} diff --git a/backend/core/urls.py b/backend/core/urls.py index 146096f..47bc788 100644 --- a/backend/core/urls.py +++ b/backend/core/urls.py @@ -12,5 +12,4 @@ urlpatterns = [ path('document//', v.document, name='document'), path('preview//', v.template_preview, name='template_preview'), path("payment/", v.payment_page, name="payment_page"), - path('documentview//', v.docprew, name='generate_document_view'), ] diff --git a/backend/core/utils.py b/backend/core/utils.py index 8e67dad..7e164ca 100644 --- a/backend/core/utils.py +++ b/backend/core/utils.py @@ -1,17 +1,19 @@ from openai import OpenAI from django.conf import settings -from .models import Risk, Control, Document, DocumentRiskControl +from .models import Risk, Control import time -def extract_risk_factors(organization): - excluded_fields={"name","email"} +def extract_organization_details(organization): + excluded_fields = {"name", "email"} risk_data = {} for field in organization._meta.get_fields(): if field.name not in excluded_fields and hasattr(organization, field.name): value = getattr(organization, field.name) if value: - risk_data[field.name] = value + help_text = getattr(field, 'help_text', '').strip() + key = help_text if help_text else field.name + risk_data[key] = value return risk_data def get_top_risk(organization): @@ -26,16 +28,21 @@ def get_top_risk(organization): Category: {risk.category} Name: {risk.risk_name} Primary Impact: {risk.primary_impact} + Secondary Impact: {risk.secondary_impact} + Tertiary Impact: {risk.tretiary_impact} + Detection Difficulty: {risk.detection_difficulty} + Recovery Complexity: {risk.recovery_complexity} + Business Impact Severity: {risk.businnes_impact_severity} """) - risk_factors = extract_risk_factors(organization) + organization_details = extract_organization_details(organization) prompt = f""" You are an AI risk assessor. Based on the following company details and list of known risks, identify the 10 most critical risks for this company. Respond only with risk IDs. Company Details: - {risk_factors} + {organization_details} List of Risks: {risk_list} @@ -56,16 +63,20 @@ def get_top_risk(organization): def get_controls_for_risk(risk, organization): client = OpenAI(api_key=settings.OPENAI_API_KEY) all_controls = Control.objects.all() - control_list = [] - - risk_factors = extract_risk_factors(organization) + organization_details = extract_organization_details(organization) + control_list = [f"Control ID: {control.id}, Control Name: {control.name}" for control in all_controls] valid_control_ids = {control.id for control in all_controls} + control_map = {control.id: control.name for control in all_controls} - for control in all_controls: - control_list.append(f"Control ID: {control.id}, Control Name: {control.name}") + def fetch_controls(prompt): + response = client.chat.completions.create( + model="gpt-4o-mini", + messages=[{"role": "system", "content": prompt}] + ) + return response.choices[0].message.content.strip() prompt = f""" - You are an expert in cybersecurity risk management. Given the risk "{risk.risk_name}" and its associated factors "{risk_factors}", + You are an expert in cybersecurity risk management. Given the risk "{risk.risk_name}" and its associated organization details "{organization_details}", your task is to select **exactly 10 unique controls** from the provided list that best mitigate this risk. Each control should be assigned a weight between **1 and 10** based on its effectiveness in reducing the risk. ### Rules: 1. **Each control ID must be unique** (no duplicates). @@ -77,68 +88,97 @@ def get_controls_for_risk(risk, organization): {control_list} ### Expected Response Format (STRICTLY FOLLOW THIS FORMAT): - ``` : : - - ``` - ### Example Correct Response (NO DUPLICATES): - ``` 12 : 8 45 : 7 - - ``` - ⚠️ **If you provide duplicate control IDs, your response will be rejected. Ensure all control IDs are unique.** ⚠️ **Follow the response format exactly. Any deviation will be considered invalid.** """ - for attempt in range(5): - response = client.chat.completions.create( - model="gpt-4o-mini", - messages=[{"role": "system", "content": prompt}] - ) - result = response.choices[0].message.content.strip() - print(f"AI Response (Attempt {attempt+1}):\n{result}") + selected_controls = [] + control_ids_seen = set() - selected_controls = [] - valid = True - control_ids_seen = set() + result = fetch_controls(prompt) + + for line in result.split("\n"): + line = line.strip() + parts = line.split(":") + if len(parts) == 2: + control_id_str = parts[0].replace("ID:", "").replace("id:", "").replace("Id:", "").strip() + weight_str = parts[1].strip().replace("Weight:", "").replace("weight:", "").strip() + print(f"Control:{control_id_str} Weight:{weight_str}") + print(f"ControlType: {type(control_id_str)} WeightType: {type(weight_str)}") + control_id_str = ''.join(filter(str.isdigit, control_id_str)) + weight_str = ''.join(filter(str.isdigit, weight_str)) + + if control_id_str and weight_str: + try: + control_id = int(control_id_str) + weight = int(weight_str) + + if control_id in valid_control_ids and 1 <= weight <= 10 and control_id not in control_ids_seen: + selected_controls.append((control_id, weight)) + control_ids_seen.add(control_id) + except ValueError: + continue + + if len(selected_controls) == 10: + return selected_controls + + while len(selected_controls) < 10: + missing_count = 10 - len(selected_controls) + remaining_controls = valid_control_ids - control_ids_seen + remaining_controls_list = [f"Control ID: {cid}, Control Name: {control_map[cid]}" for cid in remaining_controls] + + retry_prompt = f""" + You are an expert in cybersecurity risk management. Given the risk "{risk.risk_name}" and its associated organization details "{organization_details}", + your task is to select **exactly {missing_count} unique controls** from the provided list that best mitigate this risk. Each control should be assigned a weight between **1 and 10** based on its effectiveness in reducing the risk. + ### Rules: + 1. **Each control ID must be unique** (no duplicates). + 2. **Only return control IDs and weights** in the exact format below. + 3. **Weights must be between 1 and 10** (1 = low impact, 10 = high impact). + 4. **Do NOT add explanations, descriptions, or extra text.** + 5. **Ensure that control IDs are randomly distributed and diverse across different categories.** + ### Available Controls: + {remaining_controls_list} + + ### Expected Response Format (STRICTLY FOLLOW THIS FORMAT): + : + : + ### Example Correct Response (NO DUPLICATES): + 12 : 8 + 45 : 7 + ⚠️ **If you provide duplicate control IDs, your response will be rejected. Ensure all control IDs are unique.** + ⚠️ **Follow the response format exactly. Any deviation will be considered invalid.** + """ + + result = fetch_controls(retry_prompt) for line in result.split("\n"): line = line.strip() - parts = line.split(":") if len(parts) == 2: control_id_str = parts[0].replace("ID:", "").replace("id:", "").replace("Id:", "").strip() weight_str = parts[1].strip().replace("Weight:", "").replace("weight:", "").strip() - + print(f"Control:{control_id} Weight:{weight_str}") + print(f"ControlType: {type(control_id)} WeightType: {type(weight_str)}") + control_id_str = ''.join(filter(str.isdigit, control_id_str)) weight_str = ''.join(filter(str.isdigit, weight_str)) - + if control_id_str and weight_str: - control_id = int(control_id_str) - weight = int(weight_str) - - if control_id in valid_control_ids and 1 <= weight <= 10: - if control_id in control_ids_seen: - valid = False - break - selected_controls.append((control_id, weight)) - control_ids_seen.add(control_id) - else: - valid = False - break - - if valid and len(selected_controls) == 10: - return selected_controls - - print("Invalid response or duplicate control IDs found, retrying...\n") - time.sleep(2) - - print("Failed to get a valid response after multiple attempts.") - return [] - - + try: + control_id = int(control_id_str) + weight = int(weight_str) + + if control_id in valid_control_ids and 1 <= weight <= 10 and control_id not in control_ids_seen: + selected_controls.append((control_id, weight)) + control_ids_seen.add(control_id) + except ValueError: + continue + if not remaining_controls: + break + return selected_controls if len(selected_controls) == 10 else [] diff --git a/backend/core/views.py b/backend/core/views.py index 08f46b5..bd88f14 100644 --- a/backend/core/views.py +++ b/backend/core/views.py @@ -1,13 +1,13 @@ import logging -import yaml from django.shortcuts import render, redirect , get_object_or_404 from .forms import OrganizationForm -from .models import Organization,Document,Risk, DocumentTemplate,DocumentRiskControl -from backend.accounts.utils import send_confirmation_email,send_document_email +from .models import Organization,Document,Risk, DocumentTemplate +from backend.core.utils import get_top_risk +from django.urls import reverse +from backend.accounts.utils import send_confirmation_email, send_document_email from django.contrib.admin.views.decorators import staff_member_required - # @login_required # def index(request): # return HttpResponse('

Django

Página simples.

') @@ -26,6 +26,7 @@ def signup(request): if form.is_valid(): form.save() send_confirmation_email(form.data['email']) + return render(request, 'thankyou.html', { 'email': form.data['email'], }) @@ -69,27 +70,3 @@ def payment_page(request): return render(request, "payment.html", {"email": email}) - -def docprew(request, document_id): - doc = get_object_or_404(Document, id=document_id) - org = doc.organization - - document_risk_controls = DocumentRiskControl.objects.filter(document=doc) - - unique_risks = Risk.objects.filter(id__in=document_risk_controls.values('risk_id')).distinct() - - risks_with_controls = {} - - for risk in unique_risks: - related_controls = DocumentRiskControl.objects.filter(risk=risk) - - risk_controls = [control.control.name for control in related_controls] - - risks_with_controls[risk] = ", ".join(risk_controls) - - return render(request, 'document_detail.html', { - 'document': doc, - 'organization': org, - 'created_at': doc.created_at, - 'risks_with_controls': risks_with_controls, - }) \ No newline at end of file -- 2.47.3 From a8b785d4903c817abafa3696ebd0c0e6b69608ef Mon Sep 17 00:00:00 2001 From: Amir Date: Fri, 21 Feb 2025 00:53:18 +0100 Subject: [PATCH 4/4] quickfix template path --- backend/accounts/templates/accounts/confirmation_expired.html | 2 +- backend/accounts/views.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/accounts/templates/accounts/confirmation_expired.html b/backend/accounts/templates/accounts/confirmation_expired.html index 8ca56b0..66e6a9b 100644 --- a/backend/accounts/templates/accounts/confirmation_expired.html +++ b/backend/accounts/templates/accounts/confirmation_expired.html @@ -1,4 +1,4 @@ -{% extends "base_login.html" %} +{% extends "base.html" %} {%block content%}

Link has expired!

diff --git a/backend/accounts/views.py b/backend/accounts/views.py index 3c75431..901f3ea 100644 --- a/backend/accounts/views.py +++ b/backend/accounts/views.py @@ -17,10 +17,10 @@ def confirm_email(request, uuid): confirmation = get_object_or_404(EmailConfirmation, uuid=uuid) if confirmation.is_expired(): - return render(request, 'confirmation_expired.html', {'email': confirmation.email}) + return render(request, 'accounts/confirmation_expired.html', {'email': confirmation.email}) task = create_document_for_organization.delay(confirmation.email) - return render(request, 'confirmation_success.html', {'email': confirmation.email}) + return render(request, 'accounts/confirmation_success.html', {'email': confirmation.email}) def resend_confirmation(request,email): if request.method == 'POST': -- 2.47.3