diff --git a/backend/api/projects/resources.py b/backend/api/projects/resources.py index 88fe5242ce..e3ecc8b050 100644 --- a/backend/api/projects/resources.py +++ b/backend/api/projects/resources.py @@ -575,8 +575,8 @@ def setup_search_dto(request) -> ProjectSearchDTO: if request.query_params.get("basedOnMyInterests") == "true": search_dto.based_on_user_interests = authenticated_user_id - except Exception: - pass + except AttributeError as e: + logger.warning("Unable to read authenticated user details: {}", e) mapping_types_str = request.query_params.get("mappingTypes") if mapping_types_str: diff --git a/backend/api/system/general.py b/backend/api/system/general.py index b66513546b..12712e3210 100644 --- a/backend/api/system/general.py +++ b/backend/api/system/general.py @@ -255,8 +255,9 @@ async def release(db: Database = Depends(get_db)): description: Internal server error """ response = requests.get( - "https://api.github.com/repos/hotosm/tasking-manager/releases/latest" - ) + "https://api.github.com/repos/hotosm/tasking-manager/releases/latest", + timeout=30, + ).json() try: tag_name = response.json()["tag_name"] published_date = response.json()["published_at"] diff --git a/backend/main.py b/backend/main.py index 6213a65a20..b3bfe44340 100644 --- a/backend/main.py +++ b/backend/main.py @@ -40,6 +40,22 @@ async def lifespan(app): docs_url="/api/docs", ) + @_app.middleware("http") + async def add_security_headers(request: Request, call_next): + response = await call_next(request) + # Prevents MIME-sniffing + response.headers["X-Content-Type-Options"] = "nosniff" + # Prevents Clickjacking by restricting framing + response.headers["X-Frame-Options"] = "SAMEORIGIN" + # Basic Content Security Policy + response.headers["Content-Security-Policy"] = ( + "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' " + "'unsafe-inline';" + ) + # Obscures the specific server software version + response.headers["Server"] = "Tasking-Manager" + return response + # Initialize Sentry only if USE_SENTRY is enabled if settings.USE_SENTRY: sentry_sdk.init( diff --git a/backend/models/postgis/project.py b/backend/models/postgis/project.py index 08c24335be..0231d04c00 100644 --- a/backend/models/postgis/project.py +++ b/backend/models/postgis/project.py @@ -361,7 +361,7 @@ def set_country_info(self): "Referer": os.environ.get("TM_APP_BASE_URL", "https://example.com"), } try: - response = requests.get(url, headers=headers) + response = requests.get(url, headers=headers, timeout=30) response.raise_for_status() country_info = response.json() # returns a dict if country_info["address"].get("country") is not None: diff --git a/backend/services/mapping_service.py b/backend/services/mapping_service.py index 3e0b56ccae..50f6ebb142 100644 --- a/backend/services/mapping_service.py +++ b/backend/services/mapping_service.py @@ -1,5 +1,5 @@ import datetime -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET from databases import Database from fastapi import BackgroundTasks diff --git a/backend/services/project_search_service.py b/backend/services/project_search_service.py index 03c485ff62..f9c0322553 100644 --- a/backend/services/project_search_service.py +++ b/backend/services/project_search_service.py @@ -915,7 +915,11 @@ async def get_projects_geojson( project.default_locale, ) except Exception: - pass + logger.exception( + "Failed to load localized project info for project_id={}", + project.id, + ) + continue properties = { "projectId": project.id, diff --git a/desktop.ini b/desktop.ini new file mode 100644 index 0000000000..ca5df89295 --- /dev/null +++ b/desktop.ini @@ -0,0 +1,2 @@ +[.ShellClassInfo] +LocalizedResourceName=@tasking-manager-develop,0 diff --git a/frontend/src/components/header/signUp.js b/frontend/src/components/header/signUp.js index ddde704fad..850c7db999 100644 --- a/frontend/src/components/header/signUp.js +++ b/frontend/src/components/header/signUp.js @@ -88,9 +88,14 @@ const SignupForm = ({ data, setData, step, setStep }) => { }; const checkFields = () => { + if (data.email.length > 254) { + setStep({ ...step, errMessage: }); + return; + } + const re = // eslint-disable-next-line no-useless-escape - /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + /^(?=.{1,254}$)(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; if (re.test(data.email) === false) { setStep({ ...step, errMessage: }); return; diff --git a/pyproject.toml b/pyproject.toml index 85a66e37ff..a74569293d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,3 +76,6 @@ version_scheme = "pep440" version_provider = "pep621" update_changelog_on_bump = true major_version_zero = true + +[tool.bandit] +exclude_dirs = ["tests"] diff --git a/requirements.txt b/requirements.txt index 20d1ea53e8..7f2481cef1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ alembic==1.11.1 APScheduler==3.10.1 bleach==6.0.0 cachetools==5.3.1 +defusedxml fastapi==0.108.0 GeoAlchemy2==0.14.3 geojson==3.1.0 diff --git a/tests/api/helpers/test_helpers.py b/tests/api/helpers/test_helpers.py index ab9ec890ff..abdc605813 100644 --- a/tests/api/helpers/test_helpers.py +++ b/tests/api/helpers/test_helpers.py @@ -3,7 +3,7 @@ import json import logging import os -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET from typing import Tuple from backend.exceptions import NotFound diff --git a/tests/api/integration/api/tasks/test_resources.py b/tests/api/integration/api/tasks/test_resources.py index 4f971edc1c..78819513ea 100644 --- a/tests/api/integration/api/tasks/test_resources.py +++ b/tests/api/integration/api/tasks/test_resources.py @@ -1,6 +1,6 @@ # tests/api/integration/test_tasks_queries_refactored.py import base64 -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET import pytest from httpx import AsyncClient diff --git a/tests/api/integration/services/test_mapping_service.py b/tests/api/integration/services/test_mapping_service.py index e629942e4f..17798ac0a1 100644 --- a/tests/api/integration/services/test_mapping_service.py +++ b/tests/api/integration/services/test_mapping_service.py @@ -1,5 +1,5 @@ import datetime -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET from unittest.mock import patch from backend.services.project_service import ProjectService diff --git a/tests/backend/helpers/test_helpers.py b/tests/backend/helpers/test_helpers.py index 9764f54501..558bff064a 100644 --- a/tests/backend/helpers/test_helpers.py +++ b/tests/backend/helpers/test_helpers.py @@ -1,7 +1,7 @@ import base64 import json import os -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET from typing import Tuple import geojson diff --git a/tests/backend/integration/api/system/test_general.py b/tests/backend/integration/api/system/test_general.py index a9dab2447b..6fbc8159f0 100644 --- a/tests/backend/integration/api/system/test_general.py +++ b/tests/backend/integration/api/system/test_general.py @@ -17,7 +17,8 @@ def test_post_banner(self): url = "/api/v2/system/release/" response = self.client.post(url) release = requests.get( - "https://api.github.com/repos/hotosm/tasking-manager/releases/latest" + "https://api.github.com/repos/hotosm/tasking-manager/releases/latest", + timeout=30, ).json() # Assert self.assertEqual(response.status_code, 201) diff --git a/tests/backend/integration/api/tasks/test_resources.py b/tests/backend/integration/api/tasks/test_resources.py index 731e70af8c..21df2583df 100644 --- a/tests/backend/integration/api/tasks/test_resources.py +++ b/tests/backend/integration/api/tasks/test_resources.py @@ -1,4 +1,4 @@ -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET from backend.models.postgis.statuses import TaskStatus, UserRole from backend.models.postgis.task import Task diff --git a/tests/backend/integration/services/test_mapping_service.py b/tests/backend/integration/services/test_mapping_service.py index e8158d433d..1a25a7846a 100644 --- a/tests/backend/integration/services/test_mapping_service.py +++ b/tests/backend/integration/services/test_mapping_service.py @@ -1,5 +1,5 @@ import datetime -import xml.etree.ElementTree as ET +import defusedxml.ElementTree as ET from unittest.mock import patch from backend.models.postgis.task import TaskStatus