From 42763fac07722f50a5c854e0ff9da78340c5448e Mon Sep 17 00:00:00 2001 From: Krrish Sehgal <133865424+krrish-sehgal@users.noreply.github.com> Date: Sat, 21 Dec 2024 10:01:15 +0530 Subject: [PATCH 1/2] Similarity checker (#3134) --- blt/urls.py | 11 + pyproject.toml | 4 + website/similarity_utils.py | 377 ++++++++++++++++++++++ website/templates/includes/sidenav.html | 9 + website/templates/similarity.html | 398 ++++++++++++++++++++++++ website/views/organization.py | 87 ++++++ 6 files changed, 886 insertions(+) create mode 100644 website/similarity_utils.py create mode 100644 website/templates/similarity.html diff --git a/blt/urls.py b/blt/urls.py index b3c75bd14..0757f5026 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -125,6 +125,7 @@ vote_count, ) from website.views.organization import ( + CodeSimilarityAnalyze, CreateHunt, DomainDetailView, DomainList, @@ -778,6 +779,16 @@ path("teams/delete-team/", delete_team, name="delete_team"), path("teams/leave-team/", leave_team, name="leave_team"), path("teams/kick-member/", kick_member, name="kick_member"), + path( + "similarity-check/", + TemplateView.as_view(template_name="similarity.html"), + name="similarity_check", + ), + path( + "api/code-similarity/analyze/", + CodeSimilarityAnalyze.as_view(), + name="code_similarity_analyze", + ), ] if settings.DEBUG: diff --git a/pyproject.toml b/pyproject.toml index 2d6b2583f..aa3cd8293 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,10 @@ matplotlib = "^3.10.0" openpyxl = "^3.1.5" atproto = "^0.0.55" slack-bolt = "^1.22.0" +gitpython = "^3.1.43" +transformers = "^4.47.1" +torch = "^2.5.1" +scikit-learn = "^1.6.0" [tool.poetry.group.dev.dependencies] black = "^24.8.0" diff --git a/website/similarity_utils.py b/website/similarity_utils.py new file mode 100644 index 000000000..b4ff803ef --- /dev/null +++ b/website/similarity_utils.py @@ -0,0 +1,377 @@ +import ast +import csv +import difflib +import io +import os +import re + +import torch +from sklearn.metrics.pairwise import cosine_similarity +from transformers import AutoModel, AutoTokenizer + +# Initialize CodeBERT model and tokenizer +tokenizer = AutoTokenizer.from_pretrained("microsoft/codebert-base") +model = AutoModel.from_pretrained("microsoft/codebert-base") + + +def process_similarity_analysis(repo1_path, repo2_path): + """ + Process the similarity analysis between two repositories. + :param repo1_path: Path to the first repository + :param repo2_path: Path to the second repository + :return: Similarity score and matching details + """ + # Dummy data for now, will be replaced by actual parsing logic + matching_details = { + "functions": [], + "models": [], + } + + # Step 1: Extract function signatures and content + functions1 = extract_function_signatures_and_content(repo1_path) + functions2 = extract_function_signatures_and_content(repo2_path) + + # Compare functions + for func1 in functions1: + for func2 in functions2: + print(func1["signature"]["name"], func2["signature"]["name"]) + # Name similarity + name_similarity_difflib = ( + difflib.SequenceMatcher( + None, func1["signature"]["name"], func2["signature"]["name"] + ).ratio() + * 100 + ) + name_similarity_codebert = analyze_code_similarity_with_codebert( + func1["signature"]["name"], func2["signature"]["name"] + ) + name_similarity = (name_similarity_difflib + name_similarity_codebert) / 2 + + # Signature similarity + signature1 = f"{func1['signature']['name']}({', '.join(func1['signature']['args'])})" + signature2 = f"{func2['signature']['name']}({', '.join(func2['signature']['args'])})" + signature_similarity_difflib = ( + difflib.SequenceMatcher(None, signature1, signature2).ratio() * 100 + ) + signature_similarity_codebert = analyze_code_similarity_with_codebert( + signature1, signature2 + ) + signature_similarity = ( + signature_similarity_difflib + signature_similarity_codebert + ) / 2 + + # Content similarity + content_similarity = analyze_code_similarity_with_codebert( + func1["full_text"], func2["full_text"] + ) + + # Aggregate similarity + overall_similarity = (name_similarity + signature_similarity + content_similarity) / 3 + if overall_similarity > 50: # You can set the threshold here + matching_details["functions"].append( + { + "name1": func1["signature"]["name"], + "name2": func2["signature"]["name"], + "name_similarity": round(name_similarity, 2), + "signature_similarity": round(signature_similarity, 2), + "content_similarity": round(content_similarity, 2), + "similarity": round(overall_similarity, 2), + } + ) + + # Step 2: Compare Django models + models1 = extract_django_models(repo1_path) + models2 = extract_django_models(repo2_path) + + # Compare models and fields + for model1 in models1: + for model2 in models2: + model_similarity = ( + difflib.SequenceMatcher(None, model1["name"], model2["name"]).ratio() * 100 + ) + + model_fields_similarity = compare_model_fields(model1, model2) + matching_details["models"].append( + { + "name1": model1["name"], + "name2": model2["name"], + "similarity": round(model_similarity, 2), + "field_comparison": model_fields_similarity, + } + ) + + # Convert matching_details to CSV + csv_file = convert_matching_details_to_csv(matching_details) + + return matching_details, csv_file + + +def convert_matching_details_to_csv(matching_details): + """ + Convert matching details dictionary to a CSV file. + :param matching_details: Dictionary containing matching details + :return: CSV file as a string + """ + output = io.StringIO() + writer = csv.writer(output) + + # Write function similarities + writer.writerow(["Function Similarities"]) + writer.writerow( + [ + "Name1", + "Name2", + "Name Similarity", + "Signature Similarity", + "Content Similarity", + "Overall Similarity", + ] + ) + for func in matching_details["functions"]: + writer.writerow( + [ + func["name1"], + func["name2"], + func["name_similarity"], + func["signature_similarity"], + func["content_similarity"], + func["similarity"], + ] + ) + + # Write model similarities + writer.writerow([]) + writer.writerow(["Model Similarities"]) + writer.writerow(["Name1", "Name2", "Model Name Similarity", "Overall Field Similarity"]) + for model in matching_details["models"]: + writer.writerow( + [ + model["name1"], + model["name2"], + model["similarity"], + model["field_comparison"]["overall_field_similarity"], + ] + ) + + # Write field comparison details + writer.writerow( + [ + "Field1 Name", + "Field1 Type", + "Field2 Name", + "Field2 Type", + "Field Name Similarity", + "Field Type Similarity", + "Overall Similarity", + ] + ) + for field in model["field_comparison"]["field_comparison_details"]: + writer.writerow( + [ + field["field1_name"], + field["field1_type"], + field["field2_name"], + field["field2_type"], + field["field_name_similarity"], + field["field_type_similarity"], + field["overall_similarity"], + ] + ) + + return output.getvalue() + + +def analyze_code_similarity_with_codebert(code1, code2): + """ + Analyze the semantic similarity between two code snippets using CodeBERT embeddings. + :param code1: First code snippet + :param code2: Second code snippet + :return: Similarity score (0-100) + """ + + # Tokenize and encode inputs + inputs_code1 = tokenizer( + code1, return_tensors="pt", truncation=True, max_length=512, padding="max_length" + ) + inputs_code2 = tokenizer( + code2, return_tensors="pt", truncation=True, max_length=512, padding="max_length" + ) + + # Generate embeddings + with torch.no_grad(): + outputs_code1 = model(**inputs_code1) + outputs_code2 = model(**inputs_code2) + + # Use mean pooling over the last hidden state to get sentence-level embeddings + embedding_code1 = outputs_code1.last_hidden_state.mean(dim=1) + embedding_code2 = outputs_code2.last_hidden_state.mean(dim=1) + + # Compute cosine similarity + similarity = cosine_similarity(embedding_code1.numpy(), embedding_code2.numpy()) + similarity_score = similarity[0][0] * 100 # Scale similarity to 0-100 + + return round(similarity_score, 2) + + +def extract_function_signatures_and_content(repo_path): + """ + Extract function signatures (name, parameters) and full text from Python files. + :param repo_path: Path to the repository + :return: List of function metadata (signature + full text) + """ + functions = [] + for root, dirs, files in os.walk(repo_path): + for file in files: + if file.endswith(".py"): + file_path = os.path.join(root, file) + with open(file_path, "r") as f: + try: + file_content = f.read() + tree = ast.parse(file_content, filename=file) + for node in ast.walk(tree): + if isinstance(node, ast.FunctionDef): + signature = { + "name": node.name, + "args": [arg.arg for arg in node.args.args], + "defaults": [ + ast.dump(default) for default in node.args.defaults + ], + } + # Extract function body as full text + function_text = ast.get_source_segment(file_content, node) + function_data = { + "signature": signature, + "full_text": function_text, # Full text of the function + } + functions.append(function_data) + except Exception as e: + print(f"Error parsing {file_path}: {e}") + return functions + + +def extract_django_models(repo_path): + """ + Extract Django model names and fields from the given repository. + :param repo_path: Path to the repository + :return: List of models with their fields + """ + models = [] + + # Walk through the repository directory + for root, dirs, files in os.walk(repo_path): + for file in files: + if file.endswith(".py"): # Only process Python files + file_path = os.path.join(root, file) + + # Open the file and read its contents + with open(file_path, "r") as f: + lines = f.readlines() + model_name = None + fields = [] + + for line in lines: + line = line.strip() + # Look for class definition that inherits from models.Model + if line.startswith("class ") and "models.Model" in line: + if model_name: # Save the previous model if exists + models.append({"name": model_name, "fields": fields}) + model_name = line.split("(")[0].replace("class ", "").strip() + fields = [] # Reset fields when a new model starts + + else: + # Match field definitions like: name = models.CharField(max_length=...) + match = re.match(r"^\s*(\w+)\s*=\s*models\.(\w+)", line) + if match: + field_name = match.group(1) + field_type = match.group(2) + fields.append({"field_name": field_name, "field_type": field_type}) + + # Match other field types like ForeignKey, ManyToManyField, etc. + match_complex = re.match( + r"^\s*(\w+)\s*=\s*models\.(ForeignKey|ManyToManyField|OneToOneField)\((.*)\)", + line, + ) + if match_complex: + field_name = match_complex.group(1) + field_type = match_complex.group(2) + field_params = match_complex.group(3).strip() + fields.append( + { + "field_name": field_name, + "field_type": field_type, + "parameters": field_params, + } + ) + + # Add the last model if the file ends without another class + if model_name: + models.append({"name": model_name, "fields": fields}) + + return models + + +def compare_model_fields(model1, model2): + """ + Compare the names and fields of two Django models using difflib. + Compares model names, field names, and field types to calculate similarity scores. + + :param model1: First model's details (e.g., {'name': 'User', 'fields': [...]}) + :param model2: Second model's details (e.g., {'name': 'Account', 'fields': [...]}) + :return: Dictionary containing name and field similarity details + """ + # Compare model names + model_name_similarity = ( + difflib.SequenceMatcher(None, model1["name"], model2["name"]).ratio() * 100 + ) + + # Initialize field comparison details + field_comparison_details = [] + + # Get fields from both models + fields1 = model1.get("fields", []) + fields2 = model2.get("fields", []) + + for field1 in fields1: + for field2 in fields2: + print(field1, field2) + # Compare field names + field_name_similarity = ( + difflib.SequenceMatcher(None, field1["field_name"], field2["field_name"]).ratio() + * 100 + ) + + # Compare field types + field_type_similarity = ( + difflib.SequenceMatcher(None, field1["field_type"], field2["field_type"]).ratio() + * 100 + ) + + # Average similarity between the field name and type + overall_similarity = (field_name_similarity + field_type_similarity) / 2 + + # Append details for each field comparison + if overall_similarity > 50: + field_comparison_details.append( + { + "field1_name": field1["field_name"], + "field1_type": field1["field_type"], + "field2_name": field2["field_name"], + "field2_type": field2["field_type"], + "field_name_similarity": round(field_name_similarity, 2), + "field_type_similarity": round(field_type_similarity, 2), + "overall_similarity": round(overall_similarity, 2), + } + ) + + # Calculate overall similarity across all fields + if field_comparison_details: + total_similarity = sum([entry["overall_similarity"] for entry in field_comparison_details]) + overall_field_similarity = total_similarity / len(field_comparison_details) + else: + overall_field_similarity = 0.0 + + return { + "model_name_similarity": round(model_name_similarity, 2), + "field_comparison_details": field_comparison_details, + "overall_field_similarity": round(overall_field_similarity, 2), + } diff --git a/website/templates/includes/sidenav.html b/website/templates/includes/sidenav.html index 194d7a982..c30ef9c58 100644 --- a/website/templates/includes/sidenav.html +++ b/website/templates/includes/sidenav.html @@ -124,6 +124,15 @@ Trademarks +
  • + +
    + +
    + SimilarityScan +
    +
  • diff --git a/website/templates/similarity.html b/website/templates/similarity.html new file mode 100644 index 000000000..5ddec615a --- /dev/null +++ b/website/templates/similarity.html @@ -0,0 +1,398 @@ +{% extends "base.html" %} +{% block content %} + {% include "includes/sidenav.html" %} + + +
    +

    Similarity Check

    + +
    +
    + High Similarity +
    +
    + Medium Similarity +
    +
    + Low/No Similarity +
    +
    + +
    +
    +
    + +
    +
    + +
    +
    +
    +
    + +
    +
    + +
    +
    + + +
    + +
    +

    Results

    +
    +
    + +
    + +
    +
    + +{% endblock content %} diff --git a/website/views/organization.py b/website/views/organization.py index 3c429ffc7..7d49d781d 100644 --- a/website/views/organization.py +++ b/website/views/organization.py @@ -1,5 +1,7 @@ import ipaddress import json +import os +import tempfile from collections import defaultdict from datetime import datetime, timedelta, timezone from decimal import Decimal @@ -26,8 +28,11 @@ from django.views.decorators.http import require_POST from django.views.generic import FormView, ListView, TemplateView, View from django.views.generic.edit import CreateView +from git import Repo # Requires GitPython library from rest_framework import status from rest_framework.authtoken.models import Token +from rest_framework.response import Response +from rest_framework.views import APIView from blt import settings from website.forms import CaptchaForm, HuntForm, IpReportForm, UserProfileForm @@ -49,6 +54,7 @@ Winner, ) from website.services.blue_sky_service import BlueSkyService +from website.similarity_utils import process_similarity_analysis from website.utils import format_timedelta, get_client_ip, get_github_issue_title @@ -1816,3 +1822,84 @@ def checkIN_detail(request, report_id): "blockers": report.blockers, } return render(request, "sizzle/checkin_detail.html", context) + + +class CodeSimilarityAnalyze(APIView): + def post(self, request, *args, **kwargs): + # Extract and validate data from request + type1 = request.data.get("type1") # 'github' or 'zip' + type2 = request.data.get("type2") # 'github' or 'zip' + + if type1 == "github": + repo1 = request.data.get("repo1") # GitHub URL + elif type1 == "zip": + repo1 = request.FILES.get("repo1") # ZIP file + + if type2 == "github": + repo2 = request.data.get("repo2") # GitHub URL + elif type2 == "zip": + repo2 = request.FILES.get("repo2") # ZIP file + + if not repo1 or not repo2 or not type1 or not type2: + return Response( + {"error": "Both repositories and their types are required."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + if type1 not in ["github", "zip"] or type2 not in ["github", "zip"]: + return Response( + {"error": "Invalid type. Must be 'github' or 'zip'."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + try: + temp_dir = tempfile.mkdtemp() + repo1_path = self.download_or_extract(repo1, type1, temp_dir, "repo1") + repo2_path = self.download_or_extract(repo2, type2, temp_dir, "repo2") + + matching_details, csv_file = process_similarity_analysis(repo1_path, repo2_path) + + except ValueError as e: + return Response( + {"error": "An unexpected error occurred, please try again later."}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + except Exception as e: + # return Response({"error": str(e)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + return Response( + {"error": "An unexpected error occurred, please try again later."}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR, + ) + response = Response( + { + "status": "success", + "matching_details": matching_details, # Detailed function/model similarity + }, + status=status.HTTP_200_OK, + ) + + # response["Content-Disposition"] = 'attachment; filename="similarity_report.csv"' + # response["Content-Type"] = "text/csv" + # response.content = csv_file + + return response + + def download_or_extract(self, source, source_type, temp_dir, repo_name): + """ + Download or extract the repository based on the type (GitHub or ZIP). + :param source: GitHub URL or ZIP file path + :param source_type: "github" or "zip" + :param temp_dir: Temporary directory for processing + :param repo_name: Prefix for naming (repo1 or repo2) + :return: Path to the extracted repository + """ + + dest_path = os.path.join(temp_dir, repo_name) + if source_type == "github": + # Clone the GitHub repository + Repo.clone_from(source, dest_path) + + elif source_type == "zip": + pass + + return dest_path From f5b655adaa70f035bea5e391261aea6e07a18c68 Mon Sep 17 00:00:00 2001 From: Altafur Rahman Date: Sat, 21 Dec 2024 20:32:58 +0600 Subject: [PATCH 2/2] rename 'company' to 'organization' in templates and URLs (#3143) --- blt/urls.py | 124 ++++++++++-------- website/signals.py | 17 ++- website/static/company/js/hunt_controller.js | 4 +- website/static/js/scripts.js | 12 +- .../templates/admin_dashboard_company.html | 4 +- .../bughunt/company_manage_bughunts.html | 8 +- .../templates/company/company_analytics.html | 6 +- .../company/company_includes/navbar.html | 2 +- .../company/company_includes/sidebar.html | 12 +- .../company/company_manage_roles.html | 2 +- .../company_dashboard_hunt_detail.html | 2 +- website/templates/company_domain_lists.html | 2 +- website/templates/domain.html | 2 +- website/templates/hunt_drafts.html | 2 +- website/templates/hunt_previous.html | 2 +- website/templates/includes/admin_sidenav.html | 2 +- .../templates/includes/company_sidenav.html | 4 +- website/templates/includes/header.html | 2 +- website/templates/includes/navbar.html | 2 +- website/templates/includes/sidenav.html | 4 +- website/templates/sitemap.html | 32 ++--- website/views/company.py | 42 +++--- 22 files changed, 156 insertions(+), 133 deletions(-) diff --git a/blt/urls.py b/blt/urls.py index 0757f5026..41c7631a2 100644 --- a/blt/urls.py +++ b/blt/urls.py @@ -260,9 +260,9 @@ urlpatterns = [ path("", home, name="home"), path( - "api/v1/companies/", + "api/v1/organizations/", OrganizationViewSet.as_view({"get": "list", "post": "create"}), - name="company", + name="organization", ), path("invite-friend/", invite_friend, name="invite_friend"), path("referral/", referral_signup, name="referral_signup"), @@ -290,7 +290,9 @@ path("auth/google/url/", google_views.oauth2_login), path("auth/facebook/url/", facebook_views.oauth2_callback), path("socialaccounts/", SocialAccountListView.as_view(), name="social_account_list"), - path("add_domain_to_company/", add_domain_to_organization, name="add_domain_to_company"), + path( + "add_domain_to_organization/", add_domain_to_organization, name="add_domain_to_organization" + ), path( "socialaccounts//disconnect/", SocialAccountDisconnectView.as_view(), @@ -309,9 +311,9 @@ re_path(r"^redoc/$", schema_view.with_ui("redoc", cache_timeout=0), name="schema-redoc"), re_path(r"^issues/$", newhome, name="issues"), re_path( - r"^dashboard/company/$", + r"^dashboard/organization/$", organization_dashboard, - name="company_dashboard_home", + name="organization_dashboard_home", ), re_path( r"^dashboard/user/profile/addbalance$", @@ -325,29 +327,29 @@ name="stripe_connected", ), re_path( - r"^dashboard/admin/company$", + r"^dashboard/admin/organization$", admin_organization_dashboard, - name="admin_company_dashboard", + name="admin_organization_dashboard", ), re_path( - r"^dashboard/admin/company/addorupdate$", + r"^dashboard/admin/organization/addorupdate$", add_or_update_organization, - name="add_or_update_company", + name="add_or_update_organization", ), re_path( - r"^dashboard/company/domain/addorupdate$", + r"^dashboard/organization/domain/addorupdate$", add_or_update_domain, name="add_or_update_domain", ), path( - "dashboard/company/domain//", + "dashboard/organization/domain//", organization_dashboard_domain_detail, - name="company_dashboard_domain_detail", + name="organization_dashboard_domain_detail", ), path( - "dashboard/company/hunt//", + "dashboard/organization/hunt//", organization_dashboard_hunt_detail, - name="company_dashboard_hunt_detail", + name="organization_dashboard_hunt_detail", ), path("dashboard/user/hunt//", view_hunt, name="view_hunt"), path( @@ -361,52 +363,52 @@ name="hunt_results", ), path( - "dashboard/company/hunt//edit", + "dashboard/organization/hunt//edit", organization_dashboard_hunt_edit, - name="company_dashboard_hunt_edit", + name="organization_dashboard_hunt_edit", ), path( - "dashboard/admin/company//", + "dashboard/admin/organization//", admin_organization_dashboard_detail, - name="admin_company_dashboard_detail", + name="admin_organization_dashboard_detail", ), - re_path(r"^dashboard/company/hunt/create$", CreateHunt.as_view(), name="create_hunt"), + re_path(r"^dashboard/organization/hunt/create$", CreateHunt.as_view(), name="create_hunt"), path("hunt/", ShowBughuntView.as_view(), name="show_bughunt"), - re_path(r"^dashboard/company/hunt/drafts$", DraftHunts.as_view(), name="draft_hunts"), + re_path(r"^dashboard/organization/hunt/drafts$", DraftHunts.as_view(), name="draft_hunts"), re_path( - r"^dashboard/company/hunt/upcoming$", + r"^dashboard/organization/hunt/upcoming$", UpcomingHunts.as_view(), name="upcoming_hunts", ), re_path( - r"^dashboard/company/hunt/previous$", + r"^dashboard/organization/hunt/previous$", PreviousHunts.as_view(), name="previous_hunts", ), path( - "dashboard/company/hunt/previous//", + "dashboard/organization/hunt/previous//", organization_hunt_results, - name="company_hunt_results", + name="organization_hunt_results", ), re_path( - r"^dashboard/company/hunt/ongoing$", + r"^dashboard/organization/hunt/ongoing$", OngoingHunts.as_view(), name="ongoing_hunts", ), - re_path(r"^dashboard/company/domains$", DomainList.as_view(), name="domain_list"), + re_path(r"^dashboard/organization/domains$", DomainList.as_view(), name="domain_list"), re_path( - r"^dashboard/company/settings$", + r"^dashboard/organization/settings$", OrganizationSettings.as_view(), - name="company-settings", + name="organization-settings", ), re_path(r"^join$", Joinorganization.as_view(), name="join"), re_path( - r"^dashboard/company/settings/role/update$", + r"^dashboard/organization/settings/role/update$", update_role, name="update-role", ), re_path( - r"^dashboard/company/settings/role/add$", + r"^dashboard/organization/settings/role/add$", add_role, name="add-role", ), @@ -652,68 +654,78 @@ # users path("users/", users_view, name="users"), # company specific urls : - path("company/", RegisterOrganizationView.as_view(), name="register_company"), - path("company/dashboard/", Organization_view, name="company_view"), + path("organization/", RegisterOrganizationView.as_view(), name="register_organization"), + path("organization/dashboard/", Organization_view, name="organization_view"), path( - "company//dashboard/analytics/", + "organization//dashboard/analytics/", OrganizationDashboardAnalyticsView.as_view(), - name="company_analytics", + name="organization_analytics", ), path( - "company//dashboard/integrations/", + "organization//dashboard/integrations/", OrganizationDashboardIntegrations.as_view(), - name="company_manage_integrations", + name="organization_manage_integrations", ), path( - "company//dashboard/bugs/", + "organization//dashboard/bugs/", OrganizationDashboardManageBugsView.as_view(), - name="company_manage_bugs", + name="organization_manage_bugs", ), path( - "company//dashboard/domains/", + "organization//dashboard/domains/", OrganizationDashboardManageDomainsView.as_view(), - name="company_manage_domains", + name="organization_manage_domains", ), path( - "company//dashboard/roles/", + "organization//dashboard/roles/", OrganizationDashboardManageRolesView.as_view(), - name="company_manage_roles", + name="organization_manage_roles", ), path( - "company//dashboard/bughunts/", + "organization//dashboard/bughunts/", OrganizationDashboardManageBughuntView.as_view(), - name="company_manage_bughunts", + name="organization_manage_bughunts", + ), + path( + "organization/dashboard/end_bughunt/", EndBughuntView.as_view(), name="end_bughunt" ), - path("company/dashboard/end_bughunt/", EndBughuntView.as_view(), name="end_bughunt"), - path("company//dashboard/add_bughunt/", AddHuntView.as_view(), name="add_bughunt"), - path("company//dashboard/add_domain/", AddDomainView.as_view(), name="add_domain"), + path("organization//dashboard/add_bughunt/", AddHuntView.as_view(), name="add_bughunt"), + path("organization//dashboard/add_domain/", AddDomainView.as_view(), name="add_domain"), path( - "company//dashboard/add_slack_integration/", + "organization//dashboard/add_slack_integration/", AddSlackIntegrationView.as_view(), name="add_slack_integration", ), path( - "company//dashboard/edit_domain//", + "organization//dashboard/edit_domain//", AddDomainView.as_view(), name="edit_domain", ), - path("company/domain//", login_required(DomainView.as_view()), name="view_domain"), - path("company/delete_prize//", delete_prize, name="delete_prize"), - path("company/edit_prize//", edit_prize, name="edit_prize"), - path("company/accept_bug///", accept_bug, name="accept_bug"), + path("organization/domain//", login_required(DomainView.as_view()), name="view_domain"), + path( + "organization/delete_prize//", + delete_prize, + name="delete_prize", + ), + path( + "organization/edit_prize//", + edit_prize, + name="edit_prize", + ), + path("organization/accept_bug///", accept_bug, name="accept_bug"), path( - "company/accept_bug///", + "organization/accept_bug///", accept_bug, name="accept_bug_no_reward", ), path( - "company/delete_manager///", + "organization/delete_manager///", delete_manager, name="delete_manager", ), path("sponsor/", sponsor_view, name="sponsor"), path("donate/", donate_view, name="donate"), - path("companies/", DomainListView.as_view(), name="domain_lists"), + path("organizations/", DomainListView.as_view(), name="domain_lists"), path("trademarks/", trademark_search, name="trademark_search"), path("generate_bid_image//", generate_bid_image, name="generate_bid_image"), path("bidding/", SaveBiddingData, name="BiddingData"), diff --git a/website/signals.py b/website/signals.py index 43e40f19d..62bc9b2d2 100644 --- a/website/signals.py +++ b/website/signals.py @@ -74,9 +74,20 @@ def handle_post_save(sender, instance, created, **kwargs): assign_first_action_badge(instance.user, "First Bug Reported") create_activity(instance, "created") - elif sender == Hunt and created: # Track first bid placed - assign_first_action_badge(instance.user, "First Bug Bounty") - create_activity(instance, "created") + elif sender == Hunt and created: # Track first bug bounty + # Attempt to get the user from Domain managers or Organization + user = None + if instance.domain: + # Try managers of the domain + user = instance.domain.managers.first() + # Optionally, if Organization has a user, fetch it here + if not user and instance.domain.organization: + user = getattr(instance.domain.organization, "user", None) + + # Assign badge and activity if a user is found + if user: + assign_first_action_badge(user, "First Bug Bounty") + create_activity(instance, "created") elif sender == Suggestion and created: # Track first suggestion assign_first_action_badge(instance.user, "First Suggestion") diff --git a/website/static/company/js/hunt_controller.js b/website/static/company/js/hunt_controller.js index 91efc4c78..7da36bc31 100644 --- a/website/static/company/js/hunt_controller.js +++ b/website/static/company/js/hunt_controller.js @@ -221,7 +221,7 @@ function removePrize(event, prizeId, companyId) { prizeContainer.appendChild(loadingIndicator); // Make AJAX call to delete the prize with company_id - fetch(`/company/delete_prize/${prizeId}/${companyId}`, { + fetch(`/organization/delete_prize/${prizeId}/${companyId}`, { method: 'DELETE', headers: { 'X-CSRFToken': getCookie('csrftoken') @@ -284,7 +284,7 @@ function updatePrize(prizeId, companyId) { } // Make AJAX call to update the prize with company_id - fetch(`/company/edit_prize/${prizeId}/${companyId}`, { + fetch(`/organization/edit_prize/${prizeId}/${companyId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', diff --git a/website/static/js/scripts.js b/website/static/js/scripts.js index 094d73549..4f908540b 100644 --- a/website/static/js/scripts.js +++ b/website/static/js/scripts.js @@ -20,7 +20,7 @@ console.log(serializedData) $.ajax({ type: 'POST', - url: "/dashboard/company/settings/role/update", + url: "/dashboard/organization/settings/role/update", data: serializedData, success: function (response) { window.location.reload(); @@ -46,7 +46,7 @@ var value = ($( this ).serializeArray())[1].value; $.ajax({ type: 'POST', - url: "/dashboard/company/hunt/"+value+"/edit", + url: "/dashboard/organization/hunt/"+value+"/edit", data: serializedData, success: function (response) { window.location.reload(); @@ -113,7 +113,7 @@ serializedData.push({name:"date2", value: date2}); $.ajax({ type: 'POST', - url: "/dashboard/company/hunt/create", + url: "/dashboard/organization/hunt/create", data: $.param(serializedData), success: function (response) { window.location.reload(); @@ -131,7 +131,7 @@ console.log(serializedData) $.ajax({ type: 'POST', - url: "/dashboard/company/settings/role/add", + url: "/dashboard/organization/settings/role/add", data: serializedData, success: function (response) { window.location.reload(); @@ -150,7 +150,7 @@ console.log(serializedData) $.ajax({ type: 'POST', - url: "/dashboard/admin/company/addorupdate", + url: "/dashboard/admin/organization/addorupdate", data: serializedData, success: function (response) { window.location.reload(); @@ -169,7 +169,7 @@ console.log(serializedData) $.ajax({ type: 'POST', - url: "/dashboard/company/domain/addorupdate", + url: "/dashboard/organization/domain/addorupdate", data: serializedData, success: function (response) { console.log(response) diff --git a/website/templates/admin_dashboard_company.html b/website/templates/admin_dashboard_company.html index 11251c3cb..518f928ef 100644 --- a/website/templates/admin_dashboard_company.html +++ b/website/templates/admin_dashboard_company.html @@ -26,10 +26,10 @@ Organization {% for company in companys %} {% if company.is_active %} - {{ company.name }} {% else %} - {{ company.name }} {% endif %} {% endfor %} diff --git a/website/templates/company/bughunt/company_manage_bughunts.html b/website/templates/company/bughunt/company_manage_bughunts.html index 4695e7b28..86457404e 100644 --- a/website/templates/company/bughunt/company_manage_bughunts.html +++ b/website/templates/company/bughunt/company_manage_bughunts.html @@ -51,19 +51,19 @@

    Bughunts

    diff --git a/website/templates/company/company_analytics.html b/website/templates/company/company_analytics.html index 574999e16..8cecf2da2 100644 --- a/website/templates/company/company_analytics.html +++ b/website/templates/company/company_analytics.html @@ -26,7 +26,7 @@
    - +

    {{ total_info.total_domains }}

    Domains

    diff --git a/website/templates/company/company_includes/navbar.html b/website/templates/company/company_includes/navbar.html index 9b7fc432b..4bbdd120a 100644 --- a/website/templates/company/company_includes/navbar.html +++ b/website/templates/company/company_includes/navbar.html @@ -68,7 +68,7 @@

    Organizations

    {% for company in companies %} - {{ company_obj.name | slice:":15" | c