From 1669ec5546334a72c10d2894090afd1879c55c42 Mon Sep 17 00:00:00 2001 From: ITQ Date: Sun, 2 Mar 2025 17:07:02 +0300 Subject: [PATCH] who did this --- services/backend/apps/task/tasks.py | 178 +++++++--------------------- 1 file changed, 40 insertions(+), 138 deletions(-) diff --git a/services/backend/apps/task/tasks.py b/services/backend/apps/task/tasks.py index a4a8eee..0c0a6a9 100644 --- a/services/backend/apps/task/tasks.py +++ b/services/backend/apps/task/tasks.py @@ -1,148 +1,50 @@ -import ast -import contextlib -import hashlib -import os -import sys -import tempfile -from io import StringIO +import requests +from celery import shared_task +from django.core.files.base import ContentFile -from config.celery import app -from apps.task.models import CompetitionTaskSubmission - -ALLOWED_MODULES = { - "pandas", - "numpy", - "matplotlib", - "seaborn", - "scipy", - "sklearn", - "datetime", - "json", - "csv", - "math", - "statistics", - "statsmodels", -} +from django.conf import settings -class SecurityException(Exception): - pass +@shared_task(bind=True, max_retries=3) +def analyze_data_task(self, submission_id): + from .models import CompetitionTaskSubmission - -def validate_code(code_str): + submission = CompetitionTaskSubmission.objects.get(id=submission_id) try: - tree = ast.parse(code_str) - except SyntaxError as e: - raise SecurityException(f"Syntax error: {e!s}") + code = submission.content.read().decode() + files = [ + (f.name, f.file.open("rb")) + for f in submission.task.attachments.filter(public=True) + ] - class ImportVisitor(ast.NodeVisitor): - def visit_Import(self, node): - for alias in node.names: - module = alias.name.split(".")[0] - if module not in ALLOWED_MODULES: - raise SecurityException(f"Disallowed import: {module}") + response = requests.post( + f"{settings.CHECKER_API_ENDPOINT}/execute", + files=[("files", (f.name, f)) for f in files] + + [ + ("code", code), + ("expected_hash", submission.task.correct_answer_hash), + ], + timeout=30, + ) + response.raise_for_status() + result = response.json() - def visit_ImportFrom(self, node): - if node.module: - module = node.module.split(".")[0] - if module not in ALLOWED_MODULES: - raise SecurityException( - f"Disallowed import from: {module}" - ) - - class SecurityVisitor(ast.NodeVisitor): - def generic_visit(self, node): - if isinstance(node, (ast.Call, ast.Attribute)): - if "system" in getattr(node, "attr", ""): - raise SecurityException("Dangerous system call detected") - super().generic_visit(node) - - try: - ImportVisitor().visit(tree) - SecurityVisitor().visit(tree) - except SecurityException: - raise - except Exception as e: - raise SecurityException(f"Security check failed: {e!s}") - - -def secure_exec(code_str, result_path, input_files=None): - original_dir = os.getcwd() - original_stdout = sys.stdout - sys.stdout = captured_stdout = StringIO() - result_content = None - - if input_files is None: - input_files = [] - - with tempfile.TemporaryDirectory() as temp_dir: - try: - os.chdir(temp_dir) - - for file in input_files: - file_path = os.path.join(temp_dir, file["bind_at"]) - os.makedirs(os.path.dirname(file_path), exist_ok=True) - with open(file_path, "wb") as f: - f.write(file["content"]) - - restricted_globals = { - "__builtins__": { - "open": open, - "print": print, - "str": str, - "int": int, - "float": float, - "bool": bool, - "list": list, - "dict": dict, - "tuple": tuple, - "set": set, - } - } - - exec(code_str, restricted_globals) - - if result_path == "stdout": - result_content = captured_stdout.getvalue().encode("utf-8") - else: - with open(result_path, "rb") as f: - result_content = f.read() - - except Exception as e: - raise RuntimeError(f"Execution error: {e!s}") - finally: - os.chdir(original_dir) - sys.stdout = original_stdout - - return result_content - - -@app.task(bind=True) -def analyze_data_task( - self, code_str, result_path, expected_file_link, submission_id, input_files=[] -): - try: - validate_code(code_str) - - result_content = secure_exec(code_str, result_path, input_files) - - result_hash = hashlib.sha256(result_content).hexdigest() - expected_hash = hashlib.sha256(expected_bytes).hexdigest() - - with contextlib.suppress(CompetitionTaskSubmission.DoesNotExist): - submission = CompetitionTaskSubmission.objects.get(id=submission_id) - submission.result = {"correct": True} - - return { - "success": True, - "match": result_hash == expected_hash, - "result_hash": result_hash, - "expected_hash": expected_hash, + submission.stdout.save("output.txt", ContentFile(result["output"])) + submission.result = { + "correct": result["hash_match"], + "result_hash": result["result_hash"], + "error": result.get("error"), } + submission.earned_points = ( + submission.task.points if result["hash_match"] else 0 + ) + submission.status = CompetitionTaskSubmission.StatusChoices.CHECKED - except SecurityException as e: - return {"success": False, "error": f"Security violation: {e!s}"} - except RuntimeError as e: - return {"success": False, "error": f"Execution error: {e!s}"} + except requests.exceptions.RequestException as e: + self.retry(countdown=2**self.request.retries) except Exception as e: - return {"success": False, "error": f"Unexpected error: {e!s}"} + submission.result = {"error": str(e)} + submission.status = CompetitionTaskSubmission.StatusChoices.CHECKED + submission.earned_points = 0 + finally: + submission.save()