""" Tenant-aware middleware for multi-tenancy """ from django.http import HttpResponseRedirect from django.urls import reverse from django.utils.deprecation import MiddlewareMixin class TenantMiddleware(MiddlewareMixin): """ Middleware that sets the current hospital context from the authenticated user. This middleware ensures that: - authenticated users have their tenant_hospital set from their profile - PX admins can switch between hospitals via session - PX admins without a selected hospital are redirected to select-hospital - Source Users have their source context available - All requests have tenant context available """ EXEMPT_PATHS = [ "/core/select-hospital/", "/accounts/logout/", "/accounts/password_reset/", "/accounts/password_reset/done/", "/accounts/reset/", "/api/", "/health/", "/admin/", "/__debug__/", ] def process_request(self, request): """Set tenant hospital context on each request.""" if request.user and request.user.is_authenticated: request.user_roles = request.user.get_role_names() request.source_user = None request.source_user_profile = None if request.user.is_source_user(): profile = request.user.get_source_user_profile_active() if profile: request.source_user = profile request.source_user_profile = profile if request.user.is_px_admin(): hospital_id = request.session.get("selected_hospital_id") if hospital_id: from apps.organizations.models import Hospital try: request.tenant_hospital = Hospital.objects.get(id=hospital_id) except Hospital.DoesNotExist: request.tenant_hospital = None request.session.pop("selected_hospital_id", None) else: request.tenant_hospital = None else: request.tenant_hospital = request.user.hospital else: request.tenant_hospital = None request.user_roles = [] request.source_user = None request.source_user_profile = None return None def process_view(self, request, view_func, view_args, view_kwargs): """Redirect PX admins without hospital to select-hospital page.""" if not request.user or not request.user.is_authenticated: return None if not request.user.is_px_admin(): return None if request.tenant_hospital: return None path = request.path for exempt_path in self.EXEMPT_PATHS: if path.startswith(exempt_path): return None if request.headers.get("x-requested-with") == "XMLHttpRequest": return None return HttpResponseRedirect(reverse("core:select_hospital")) class DepartmentRespondentMiddleware(MiddlewareMixin): """ Restrict Department Respondent users to only their department detail page and inquiry department response pages. """ ALLOWED_PATH_PREFIXES = [ "/", "/accounts/logout/", "/accounts/password_reset/", "/accounts/reset/", "/core/select-hospital/", "/organizations/departments/", "/inquiries/", "/api/", "/health/", "/admin/", "/__debug__/", "/i18n/", "/static/", ] def process_view(self, request, view_func, view_args, view_kwargs): if not request.user or not request.user.is_authenticated: return None if not request.user.is_champion(): return None if request.user.is_px_admin() or request.user.is_hospital_admin() or request.user.is_department_manager() or request.user.is_director(): return None if request.headers.get("x-requested-with") == "XMLHttpRequest": return None path = request.path if path == "/": return None for prefix in self.ALLOWED_PATH_PREFIXES: if path.startswith(prefix): if path.startswith("/organizations/departments/"): if "set-respondent" in path or "edit" in path or "delete" in path: from django.http import HttpResponseForbidden return HttpResponseForbidden() return None if path.startswith("/inquiries/"): if not path.endswith("/department-response/"): from apps.complaints.models import Inquiry pk = view_kwargs.get("pk") if pk: try: inquiry = Inquiry.objects.get(pk=pk) user_dept = request.user.department if inquiry.department == user_dept or inquiry.outgoing_department == user_dept: return None except Inquiry.DoesNotExist: pass from django.http import HttpResponseForbidden return HttpResponseForbidden() return None return None from django.http import HttpResponseForbidden return HttpResponseForbidden()