diff --git a/envipath/settings.py b/envipath/settings.py index 2d1da4fb..b14e67e4 100644 --- a/envipath/settings.py +++ b/envipath/settings.py @@ -92,7 +92,7 @@ TEMPLATES = [ }, ] -ALLOWED_HTML_TAGS = {'b', 'i', 'u', 'a'} +ALLOWED_HTML_TAGS = {'b', 'i', 'u', 'a', 'br', 'em', 'mark', 'p', 's', 'strong'} WSGI_APPLICATION = "envipath.wsgi.application" diff --git a/epdb/logic.py b/epdb/logic.py index 530ebc51..2f7ab53b 100644 --- a/epdb/logic.py +++ b/epdb/logic.py @@ -4,6 +4,7 @@ import json from typing import Union, List, Optional, Set, Dict, Any from uuid import UUID +import nh3 from django.contrib.auth import get_user_model from django.db import transaction from django.conf import settings as s @@ -184,6 +185,12 @@ class UserManager(object): def create_user( username, email, password, set_setting=True, add_to_group=True, *args, **kwargs ): + # Clean for potential XSS + clean_username = nh3.clean(username).strip() + clean_email = nh3.clean(email).strip() + if clean_username != username or clean_email != email: + # This will be caught by the try in view.py/register + raise ValueError("Invalid username or password") # avoid circular import :S from .tasks import send_registration_mail @@ -261,8 +268,9 @@ class GroupManager(object): @staticmethod def create_group(current_user, name, description): g = Group() - g.name = name - g.description = description + # Clean for potential XSS + g.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() + g.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() g.owner = current_user g.save() @@ -517,8 +525,9 @@ class PackageManager(object): @transaction.atomic def create_package(current_user, name: str, description: str = None): p = Package() - p.name = name - p.description = description + # Clean for potential XSS + p.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() + p.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() p.save() up = UserPackagePermission() @@ -1051,28 +1060,29 @@ class SettingManager(object): model: EPModel = None, model_threshold: float = None, ): - s = Setting() - s.name = name - s.description = description - s.max_nodes = max_nodes - s.max_depth = max_depth - s.model = model - s.model_threshold = model_threshold + new_s = Setting() + # Clean for potential XSS + new_s.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() + new_s.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() + new_s.max_nodes = max_nodes + new_s.max_depth = max_depth + new_s.model = model + new_s.model_threshold = model_threshold - s.save() + new_s.save() if rule_packages is not None: for r in rule_packages: - s.rule_packages.add(r) - s.save() + new_s.rule_packages.add(r) + new_s.save() usp = UserSettingPermission() usp.user = user - usp.setting = s + usp.setting = new_s usp.permission = Permission.ALL[0] usp.save() - return s + return new_s @staticmethod def get_default_setting(user: User): diff --git a/epdb/models.py b/epdb/models.py index 83b29925..fcd579da 100644 --- a/epdb/models.py +++ b/epdb/models.py @@ -11,6 +11,7 @@ from typing import Union, List, Optional, Dict, Tuple, Set, Any from uuid import uuid4 import math import joblib +import nh3 import numpy as np from django.conf import settings as s from django.contrib.auth.models import AbstractUser @@ -790,12 +791,11 @@ class Compound(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdentifierMixin if name is None or name.strip() == "": name = f"Compound {Compound.objects.filter(package=package).count() + 1}" - - c.name = name - + # Clean for potential XSS + c.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() # We have a default here only set the value if it carries some payload if description is not None and description.strip() != "": - c.description = description.strip() + c.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() c.save() @@ -967,11 +967,11 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti raise ValueError("Unpersisted Compound! Persist compound first!") cs = CompoundStructure() + # Clean for potential XSS if name is not None: - cs.name = name - + cs.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None: - cs.description = description + cs.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() cs.smiles = smiles cs.compound = compound @@ -1144,18 +1144,18 @@ class SimpleAmbitRule(SimpleRule): if name is None or name.strip() == "": name = f"Rule {Rule.objects.filter(package=package).count() + 1}" - r.name = name - + # Clean for potential XSS + r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None and description.strip() != "": - r.description = description + r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() - r.smirks = smirks + r.smirks = nh3.clean(smirks).strip() if reactant_filter_smarts is not None and reactant_filter_smarts.strip() != "": - r.reactant_filter_smarts = reactant_filter_smarts + r.reactant_filter_smarts = nh3.clean(reactant_filter_smarts).strip() if product_filter_smarts is not None and product_filter_smarts.strip() != "": - r.product_filter_smarts = product_filter_smarts + r.product_filter_smarts = nh3.clean(product_filter_smarts).strip() r.save() return r @@ -1356,12 +1356,11 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin r = Reaction() r.package = package - + # Clean for potential XSS if name is not None and name.strip() != "": - r.name = name - + r.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None and name.strip() != "": - r.description = description + r.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() r.multi_step = multi_step @@ -1663,10 +1662,10 @@ class Pathway(EnviPathModel, AliasMixin, ScenarioMixin): if name is None or name.strip() == "": name = f"Pathway {Pathway.objects.filter(package=package).count() + 1}" - pw.name = name - + # Clean for potential XSS + pw.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None and description.strip() != "": - pw.description = description + pw.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() pw.save() try: @@ -1961,11 +1960,15 @@ class Edge(EnviPathModel, AliasMixin, ScenarioMixin): for node in end_nodes: e.end_nodes.add(node) + # Clean for potential XSS + # Cleaning technically not needed as it is also done in Reaction.create, including it here for consistency if name is None: name = f"Reaction {pathway.package.reactions.count() + 1}" + name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is None: description = s.DEFAULT_VALUES["description"] + description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() r = Reaction.create( pathway.package, @@ -2482,10 +2485,10 @@ class RuleBasedRelativeReasoning(PackageBasedModel): if name is None or name.strip() == "": name = f"RuleBasedRelativeReasoning {RuleBasedRelativeReasoning.objects.filter(package=package).count() + 1}" - rbrr.name = name - + # Clean for potential XSS + rbrr.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None and description.strip() != "": - rbrr.description = description + rbrr.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() if threshold is None or (threshold <= 0 or 1 <= threshold): raise ValueError("Threshold must be a float between 0 and 1.") @@ -2591,10 +2594,10 @@ class MLRelativeReasoning(PackageBasedModel): if name is None or name.strip() == "": name = f"MLRelativeReasoning {MLRelativeReasoning.objects.filter(package=package).count() + 1}" - mlrr.name = name - + # Clean for potential XSS + mlrr.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None and description.strip() != "": - mlrr.description = description + mlrr.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() if threshold is None or (threshold <= 0 or 1 <= threshold): raise ValueError("Threshold must be a float between 0 and 1.") @@ -2954,10 +2957,10 @@ class EnviFormer(PackageBasedModel): if name is None or name.strip() == "": name = f"EnviFormer {EnviFormer.objects.filter(package=package).count() + 1}" - mod.name = name - + # Clean for potential XSS + mod.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None and description.strip() != "": - mod.description = description + mod.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() if threshold is None or (threshold <= 0 or 1 <= threshold): raise ValueError("Threshold must be a float between 0 and 1.") @@ -3400,41 +3403,43 @@ class Scenario(EnviPathModel): scenario_type: str, additional_information: List["EnviPyModel"], ): - s = Scenario() - s.package = package + new_s = Scenario() + new_s.package = package if name is None or name.strip() == "": name = f"Scenario {Scenario.objects.filter(package=package).count() + 1}" - s.name = name + new_s.name = nh3.clean(name, tags=s.ALLOWED_HTML_TAGS).strip() if description is not None and description.strip() != "": - s.description = description + new_s.description = nh3.clean(description, tags=s.ALLOWED_HTML_TAGS).strip() if scenario_date is not None and scenario_date.strip() != "": - s.scenario_date = scenario_date + new_s.scenario_date = nh3.clean(scenario_date).strip() if scenario_type is not None and scenario_type.strip() != "": - s.scenario_type = scenario_type + new_s.scenario_type = scenario_type add_inf = defaultdict(list) for info in additional_information: cls_name = info.__class__.__name__ - ai_data = json.loads(info.model_dump_json()) + # Clean for potential XSS hidden in the additional information fields. + ai_data = json.loads(nh3.clean(info.model_dump_json()).strip()) ai_data["uuid"] = f"{uuid4()}" add_inf[cls_name].append(ai_data) - s.additional_information = add_inf + new_s.additional_information = add_inf - s.save() + new_s.save() - return s + return new_s @transaction.atomic def add_additional_information(self, data: "EnviPyModel"): cls_name = data.__class__.__name__ - ai_data = json.loads(data.model_dump_json()) + # Clean for potential XSS hidden in the additional information fields. + ai_data = json.loads(nh3.clean(data.model_dump_json()).strip()) ai_data["uuid"] = f"{uuid4()}" if cls_name not in self.additional_information: @@ -3469,7 +3474,8 @@ class Scenario(EnviPathModel): new_ais = defaultdict(list) for k, vals in data.items(): for v in vals: - ai_data = json.loads(v.model_dump_json()) + # Clean for potential XSS hidden in the additional information fields. + ai_data = json.loads(nh3.clean(v.model_dump_json()).strip()) if hasattr(v, "uuid"): ai_data["uuid"] = str(v.uuid) else: diff --git a/epdb/templatetags/envipytags.py b/epdb/templatetags/envipytags.py index 071946df..c8c92fef 100644 --- a/epdb/templatetags/envipytags.py +++ b/epdb/templatetags/envipytags.py @@ -1,7 +1,4 @@ from django import template -from django.conf import settings as s -from django.utils.safestring import mark_safe -import nh3 register = template.Library() @@ -9,11 +6,3 @@ register = template.Library() @register.filter def classname(obj): return obj.__class__.__name__ - - -@register.filter(name="nh_safe") -def nh_safe(txt): - if not isinstance(txt, str): - return txt - clean_html = nh3.clean(txt, tags=s.ALLOWED_HTML_TAGS) - return mark_safe(clean_html) diff --git a/epdb/views.py b/epdb/views.py index dc6f7f68..f9279e1a 100644 --- a/epdb/views.py +++ b/epdb/views.py @@ -58,16 +58,6 @@ def log_post_params(request): logger.debug(f"{k}\t{v}") -def clean_dict(bad_dict): - """Check each value in a dictionary for XSS attempts""" - clean = {} # TODO: I'm not sure if this is the best way to do this. - for key, value in bad_dict.items(): - if key != 'csrfmiddlewaretoken' and isinstance(value, str): - value = nh3.clean(value, tags=s.ALLOWED_HTML_TAGS) - clean[key] = value - return clean - - def error(request, message: str, detail: str, code: int = 400): context = get_base_context(request) error_context = { @@ -94,8 +84,7 @@ def login(request): from django.contrib.auth import authenticate from django.contrib.auth import login - # Check if the cleaned username is equal to the unclean username, if not, invalid username - username = nh3.clean(request.POST.get("username")).strip() + username = request.POST.get("username").strip() if username != request.POST.get("username").strip(): context["message"] = "Login failed!" return render(request, "static/login.html", context) @@ -158,15 +147,12 @@ def register(request): if next := request.POST.get("next"): context["next"] = next - # We are not allowing usernames or emails to contain any html (unlike using tags=s.ALLOWED_HTML_TAGS elsewhere) - username = nh3.clean(request.POST.get("username", "")).strip() - email = nh3.clean(request.POST.get("email", "")).strip() + username = request.POST.get("username", "").strip() + email = request.POST.get("email", "").strip() password = request.POST.get("password", "").strip() rpassword = request.POST.get("rpassword", "").strip() - # Check if cleaned username and email are equal to the unclean, if not, invalid username or email - if (not (username and email and password) or username != request.POST.get("username", "").strip() or - email != request.POST.get("email", "").strip()): + if not (username and email and password): context["message"] = "Invalid username/email/password" return render(request, "static/register.html", context) @@ -407,11 +393,8 @@ def packages(request): else: return HttpResponseBadRequest() else: - # Clean for potential XSS - package_name = nh3.clean(request.POST.get("package-name"), tags=s.ALLOWED_HTML_TAGS).strip() - package_description = nh3.clean(request.POST.get( - "package-description", s.DEFAULT_VALUES["description"] - ), tags=s.ALLOWED_HTML_TAGS).strip() + package_name = request.POST.get("package-name") + package_description = request.POST.get("package-description", s.DEFAULT_VALUES["description"]) created_package = PackageManager.create_package( current_user, package_name, package_description @@ -686,8 +669,7 @@ def search(request): if request.method == "GET": package_urls = request.GET.getlist("packages") - # Clean for potential XSS - searchterm = nh3.clean(request.GET.get("search"), tags=s.ALLOWED_HTML_TAGS).strip() + searchterm = request.GET.get("search").strip() mode = request.GET.get("mode") @@ -789,9 +771,8 @@ def package_models(request, package_uuid): elif request.method == "POST": log_post_params(request) - # Clean for potential XSS - name = nh3.clean(request.POST.get("model-name"), tags=s.ALLOWED_HTML_TAGS).strip() - description = nh3.clean(request.POST.get("model-description"), tags=s.ALLOWED_HTML_TAGS).strip() + name = request.POST.get("model-name") + description = request.POST.get("model-description") model_type = request.POST.get("model-type") @@ -936,7 +917,7 @@ def package_model(request, package_uuid, model_uuid): else: return HttpResponseBadRequest() else: - # Clean for potential XSS + # TODO: Move cleaning to property updater name = nh3.clean(request.POST.get("model-name", "").strip(), tags=s.ALLOWED_HTML_TAGS).strip() description = nh3.clean(request.POST.get("model-description", "").strip(), tags=s.ALLOWED_HTML_TAGS).strip() @@ -1040,7 +1021,7 @@ def package(request, package_uuid): else: return HttpResponseBadRequest() - # Clean for potential XSS + # TODO: Move cleaning to property updater new_package_name = nh3.clean(request.POST.get("package-name"), tags=s.ALLOWED_HTML_TAGS).strip() new_package_description = nh3.clean(request.POST.get("package-description"), tags=s.ALLOWED_HTML_TAGS).strip() @@ -1150,10 +1131,9 @@ def package_compounds(request, package_uuid): return render(request, "collections/objects_list.html", context) elif request.method == "POST": - # Clean for potential XSS - compound_name = nh3.clean(request.POST.get("compound-name"), tags=s.ALLOWED_HTML_TAGS).strip() + compound_name = request.POST.get("compound-name") compound_smiles = request.POST.get("compound-smiles").strip() - compound_description = nh3.clean(request.POST.get("compound-description"), tags=s.ALLOWED_HTML_TAGS).strip() + compound_description = request.POST.get("compound-description") c = Compound.create(current_package, compound_smiles, compound_name, compound_description) @@ -1204,7 +1184,7 @@ def package_compound(request, package_uuid, compound_uuid): return JsonResponse({"error": str(e)}, status=400) return JsonResponse({"success": current_compound.url}) - # Clean for potential XSS + # TODO: Move cleaning to property updater new_compound_name = nh3.clean(request.POST.get("compound-name", ""), tags=s.ALLOWED_HTML_TAGS).strip() new_compound_description = nh3.clean(request.POST.get("compound-description", ""), tags=s.ALLOWED_HTML_TAGS).strip() @@ -1271,10 +1251,9 @@ def package_compound_structures(request, package_uuid, compound_uuid): return render(request, "collections/objects_list.html", context) elif request.method == "POST": - # Clean for potential XSS - structure_name = nh3.clean(request.POST.get("structure-name"), tags=s.ALLOWED_HTML_TAGS).strip() + structure_name = request.POST.get("structure-name") structure_smiles = request.POST.get("structure-smiles").strip() - structure_description = nh3.clean(request.POST.get("structure-description"), tags=s.ALLOWED_HTML_TAGS).strip() + structure_description = request.POST.get("structure-description") cs = current_compound.add_structure(structure_smiles, structure_name, structure_description) @@ -1334,7 +1313,7 @@ def package_compound_structure(request, package_uuid, compound_uuid, structure_u return redirect(current_compound.url + "/structure") else: return HttpResponseBadRequest() - # Clean for potential XSS + # TODO: Move cleaning to property updater new_structure_name = nh3.clean(request.POST.get("compound-structure-name", ""), tags=s.ALLOWED_HTML_TAGS).strip() new_structure_description = nh3.clean(request.POST.get("compound-structure-description", ""), @@ -1431,9 +1410,8 @@ def package_rules(request, package_uuid): log_post_params(request) # Generic params - # Clean for potential XSS - rule_name = nh3.clean(request.POST.get("rule-name"), tags=s.ALLOWED_HTML_TAGS).strip() - rule_description = nh3.clean(request.POST.get("rule-description"), tags=s.ALLOWED_HTML_TAGS).strip() + rule_name = request.POST.get("rule-name") + rule_description = request.POST.get("rule-description") rule_type = request.POST.get("rule-type") @@ -1441,14 +1419,10 @@ def package_rules(request, package_uuid): # Obtain parameters as required by rule type if rule_type == "SimpleAmbitRule": - # Clean for potential XSS params["smirks"] = request.POST.get("rule-smirks").strip() - params["reactant_filter_smarts"] = nh3.clean(request.POST.get("rule-reactant-smarts"), - tags=s.ALLOWED_HTML_TAGS).strip() - params["product_filter_smarts"] = nh3.clean(request.POST.get("rule-product-smarts"), - tags=s.ALLOWED_HTML_TAGS).strip() + params["reactant_filter_smarts"] = request.POST.get("rule-reactant-smarts") + params["product_filter_smarts"] = request.POST.get("rule-product-smarts") elif rule_type == "SimpleRDKitRule": - # Clean for potential XSS params["reaction_smarts"] = request.POST.get("rule-reaction-smarts").strip() elif rule_type == "ParallelRule": pass @@ -1542,7 +1516,7 @@ def package_rule(request, package_uuid, rule_uuid): return JsonResponse({"success": current_rule.url}) - # Clean for potential XSS + # TODO: Move cleaning to property updater rule_name = nh3.clean(request.POST.get("rule-name", ""), tags=s.ALLOWED_HTML_TAGS).strip() rule_description = nh3.clean(request.POST.get("rule-description", "").strip(), tags=s.ALLOWED_HTML_TAGS).strip() @@ -1605,9 +1579,8 @@ def package_reactions(request, package_uuid): return render(request, "collections/objects_list.html", context) elif request.method == "POST": - # Clean for potential XSS - reaction_name = nh3.clean(request.POST.get("reaction-name"), tags=s.ALLOWED_HTML_TAGS).strip() - reaction_description = nh3.clean(request.POST.get("reaction-description"), tags=s.ALLOWED_HTML_TAGS).strip() + reaction_name = request.POST.get("reaction-name") + reaction_description = request.POST.get("reaction-description") reactions_smirks = request.POST.get("reaction-smirks").strip() educts = reactions_smirks.split(">>")[0].split(".") @@ -1670,7 +1643,7 @@ def package_reaction(request, package_uuid, reaction_uuid): return JsonResponse({"success": current_reaction.url}) - # Clean for potential XSS + # TODO: Move cleaning to property updater new_reaction_name = nh3.clean(request.POST.get("reaction-name", ""), tags=s.ALLOWED_HTML_TAGS).strip() new_reaction_description = nh3.clean(request.POST.get("reaction-description", ""), tags=s.ALLOWED_HTML_TAGS).strip() @@ -1748,9 +1721,8 @@ def package_pathways(request, package_uuid): elif request.method == "POST": log_post_params(request) - # Clean for potential XSS - name = nh3.clean(request.POST.get("name"), tags=s.ALLOWED_HTML_TAGS).strip() - description = nh3.clean(request.POST.get("description"), tags=s.ALLOWED_HTML_TAGS).strip() + name = request.POST.get("name") + description = request.POST.get("description") smiles = request.POST.get("smiles", "").strip() pw_mode = request.POST.get("predict", "predict").strip() @@ -1901,7 +1873,7 @@ def package_pathway(request, package_uuid, pathway_uuid): return JsonResponse({"success": current_pathway.url}) - # Clean for potential XSS + # TODO: Move cleaning to property updater pathway_name = nh3.clean(request.POST.get("pathway-name"), tags=s.ALLOWED_HTML_TAGS).strip() pathway_description = nh3.clean(request.POST.get("pathway-description"), tags=s.ALLOWED_HTML_TAGS).strip() @@ -1983,9 +1955,8 @@ def package_pathway_nodes(request, package_uuid, pathway_uuid): return render(request, "collections/objects_list.html", context) elif request.method == "POST": - # Clean for potential XSS - node_name = nh3.clean(request.POST.get("node-name"), tags=s.ALLOWED_HTML_TAGS).strip() - node_description = nh3.clean(request.POST.get("node-description"), tags=s.ALLOWED_HTML_TAGS).strip() + node_name = request.POST.get("node-name") + node_description = request.POST.get("node-description") node_smiles = request.POST.get("node-smiles").strip() current_pathway.add_node(node_smiles, name=node_name, description=node_description) @@ -2116,9 +2087,8 @@ def package_pathway_edges(request, package_uuid, pathway_uuid): elif request.method == "POST": log_post_params(request) - # Clean for potential XSS - edge_name = nh3.clean(request.POST.get("edge-name"), tags=s.ALLOWED_HTML_TAGS).strip() - edge_description = nh3.clean(request.POST.get("edge-description"), tags=s.ALLOWED_HTML_TAGS).strip() + edge_name = request.POST.get("edge-name") + edge_description = request.POST.get("edge-description") edge_substrates = request.POST.getlist("edge-substrates") edge_products = request.POST.getlist("edge-products") @@ -2281,9 +2251,8 @@ def package_scenarios(request, package_uuid): elif request.method == "POST": log_post_params(request) - # Clean for potential XSS - scenario_name = nh3.clean(request.POST.get("scenario-name"), tags=s.ALLOWED_HTML_TAGS).strip() - scenario_description = nh3.clean(request.POST.get("scenario-description"), tags=s.ALLOWED_HTML_TAGS).strip() + scenario_name = request.POST.get("scenario-name") + scenario_description = request.POST.get("scenario-description") scenario_date_year = request.POST.get("scenario-date-year") scenario_date_month = request.POST.get("scenario-date-month") @@ -2297,7 +2266,7 @@ def package_scenarios(request, package_uuid): scenario_type = request.POST.get("scenario-type") - additional_information = HTMLGenerator.build_models(clean_dict(request.POST.dict())) + additional_information = HTMLGenerator.build_models(request.POST.dict()) additional_information = [x for sv in additional_information.values() for x in sv] new_scen = Scenario.create( @@ -2368,8 +2337,7 @@ def package_scenario(request, package_uuid, scenario_uuid): current_scenario.save() return redirect(current_scenario.url) elif hidden == "set-additional-information": - post_dict = clean_dict(request.POST.dict()) # Clean post dict inputs for potential XSS - ais = HTMLGenerator.build_models(post_dict) + ais = HTMLGenerator.build_models(request.POST.dict()) if s.DEBUG: logger.info(ais) @@ -2377,8 +2345,7 @@ def package_scenario(request, package_uuid, scenario_uuid): current_scenario.set_additional_information(ais) return redirect(current_scenario.url) elif hidden == "add-additional-information": - post_dict = clean_dict(request.POST.dict()) # Clean post dict inputs for potential XSS - ais = HTMLGenerator.build_models(post_dict) + ais = HTMLGenerator.build_models(request.POST.dict()) if len(ais.keys()) != 1: raise ValueError( @@ -2518,10 +2485,8 @@ def groups(request): return render(request, "collections/objects_list.html", context) elif request.method == "POST": - # Clean for potential XSS - group_name = nh3.clean(request.POST.get("group-name"), tags=s.ALLOWED_HTML_TAGS).strip() - group_description = nh3.clean(request.POST.get("group-description", s.DEFAULT_VALUES["description"]), - tags=s.ALLOWED_HTML_TAGS).strip() + group_name = request.POST.get("group-name") + group_description = request.POST.get("group-description", s.DEFAULT_VALUES["description"]) g = GroupManager.create_group(current_user, group_name, group_description) @@ -2611,9 +2576,8 @@ def settings(request): logger.info("Parameters received:") logger.info(f"{k}\t{v}") - # Clean for potential XSS - name = nh3.clean(request.POST.get("prediction-setting-name"), tags=s.ALLOWED_HTML_TAGS).strip() - description = nh3.clean(request.POST.get("prediction-setting-description"), tags=s.ALLOWED_HTML_TAGS).strip() + name = request.POST.get("prediction-setting-name") + description = request.POST.get("prediction-setting-description") new_default = request.POST.get("prediction-setting-new-default", "off") == "on" diff --git a/pyproject.toml b/pyproject.toml index c45eee7f..961f3aa7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ dependencies = [ "scikit-learn>=1.6.1", "sentry-sdk[django]>=2.32.0", "setuptools>=80.8.0", - "nh3==0.3.1" + "nh3==0.3.2" ] [tool.uv.sources] diff --git a/templates/collections/objects_list.html b/templates/collections/objects_list.html index 303ef876..3852ef7e 100644 --- a/templates/collections/objects_list.html +++ b/templates/collections/objects_list.html @@ -193,7 +193,7 @@
- +
diff --git a/templates/modals/objects/edit_compound_structure_modal.html b/templates/modals/objects/edit_compound_structure_modal.html index 8421be4f..76f8113d 100644 --- a/templates/modals/objects/edit_compound_structure_modal.html +++ b/templates/modals/objects/edit_compound_structure_modal.html @@ -16,12 +16,12 @@ {% csrf_token %}
- +
+ value="{{ compound_structure.description|safe }}" name="compound-structure-description">
diff --git a/templates/modals/objects/edit_group_member_modal.html b/templates/modals/objects/edit_group_member_modal.html index 81f15a13..05ef236f 100644 --- a/templates/modals/objects/edit_group_member_modal.html +++ b/templates/modals/objects/edit_group_member_modal.html @@ -40,7 +40,7 @@ {% endfor %} {% for g in groups %} - + {% endfor %} @@ -82,7 +82,7 @@ accept-charset="UTF-8" action="" data-remote="true" method="post"> {% csrf_token %}+ value="{{ model.name|safe }}">
+ value="{{ model.description|safe }}">
diff --git a/templates/modals/objects/edit_node_modal.html b/templates/modals/objects/edit_node_modal.html index 86db32ef..5160392b 100644 --- a/templates/modals/objects/edit_node_modal.html +++ b/templates/modals/objects/edit_node_modal.html @@ -16,12 +16,12 @@ {% csrf_token %}- +
diff --git a/templates/modals/objects/edit_package_modal.html b/templates/modals/objects/edit_package_modal.html index a6fcade4..78d12f11 100644 --- a/templates/modals/objects/edit_package_modal.html +++ b/templates/modals/objects/edit_package_modal.html @@ -16,12 +16,12 @@ {% csrf_token %}
- +
diff --git a/templates/modals/objects/edit_package_permissions_modal.html b/templates/modals/objects/edit_package_permissions_modal.html index af72843c..553a24c0 100644 --- a/templates/modals/objects/edit_package_permissions_modal.html +++ b/templates/modals/objects/edit_package_permissions_modal.html @@ -47,7 +47,7 @@ {% endfor %} {% for g in groups %} - + {% endfor %} @@ -101,7 +101,7 @@ accept-charset="UTF-8" action="" data-remote="true" method="post"> {% csrf_token %}
- +
+ rows="10">{{ pathway.description|safe }}
- +
+ value="{{ reaction.description|safe }}" name="reaction-description">
diff --git a/templates/modals/objects/edit_rule_modal.html b/templates/modals/objects/edit_rule_modal.html index 95c355d9..61c5c78c 100644 --- a/templates/modals/objects/edit_rule_modal.html +++ b/templates/modals/objects/edit_rule_modal.html @@ -15,12 +15,12 @@ {% csrf_token %}- +
+ value="{{ rule.description|safe }}" name="rule-description">
diff --git a/templates/modals/objects/edit_user_modal.html b/templates/modals/objects/edit_user_modal.html index dd28008e..62cfaabe 100644 --- a/templates/modals/objects/edit_user_modal.html +++ b/templates/modals/objects/edit_user_modal.html @@ -19,7 +19,7 @@ @@ -28,7 +28,7 @@ @@ -37,7 +37,7 @@ diff --git a/templates/modals/objects/evaluate_model_modal.html b/templates/modals/objects/evaluate_model_modal.html index 45afb5db..f5e6aa70 100644 --- a/templates/modals/objects/evaluate_model_modal.html +++ b/templates/modals/objects/evaluate_model_modal.html @@ -25,14 +25,14 @@ {% for obj in meta.readable_packages %} {% if obj.reviewed %} - + {% endif %} {% endfor %} {% for obj in meta.readable_packages %} {% if not obj.reviewed %} - + {% endif %} {% endfor %} diff --git a/templates/modals/objects/generic_copy_object_modal.html b/templates/modals/objects/generic_copy_object_modal.html index 588a26e3..112d7279 100644 --- a/templates/modals/objects/generic_copy_object_modal.html +++ b/templates/modals/objects/generic_copy_object_modal.html @@ -19,7 +19,7 @@ data-width='100%'> {% for p in meta.writeable_packages %} - ` + ` {% endfor %} diff --git a/templates/modals/objects/generic_set_aliases_modal.html b/templates/modals/objects/generic_set_aliases_modal.html index cbf8a526..0253467e 100644 --- a/templates/modals/objects/generic_set_aliases_modal.html +++ b/templates/modals/objects/generic_set_aliases_modal.html @@ -56,7 +56,7 @@ -