Coverage for src/backend/InvenTree/report/fetcher.py: 21%

31 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-07 17:47 +0000

1"""WeasyPrint URL fetcher with security restrictions for report generation.""" 

2 

3from urllib.parse import urlparse 

4 

5import structlog 

6from weasyprint.urls import URLFetcher 

7 

8logger = structlog.get_logger('inventree') 

9 

10 

11class InvenTreeURLFetcher(URLFetcher): 

12 """WeasyPrint URL fetcher restricted to safe origins.""" 

13 

14 def __init__(self, **kwargs): 

15 """Disable redirect following so a same-origin URL cannot be used for SSRF.""" 

16 kwargs.setdefault('allow_redirects', False) 

17 super().__init__(**kwargs) 

18 

19 def fetch(self, url, headers=None): 

20 """Validate *url* before delegating to the parent fetcher.""" 

21 parsed = urlparse(url) 

22 scheme = parsed.scheme.lower() 

23 

24 if scheme in ('data', 'http', 'https'): 

25 self._validate_http_url(url, parsed) 

26 return super().fetch(url, headers) 

27 

28 if scheme == 'file': 

29 logger.warning("InvenTreeURLFetcher: blocked file:// URL: '%s'", url) 

30 raise ValueError( 

31 f'file:// URLs are not permitted in report templates: {url}' 

32 ) 

33 

34 raise ValueError(f"URL scheme '{scheme}' is not permitted in report templates") 

35 

36 def _validate_http_url(self, url: str, parsed) -> None: 

37 """Raise if HTTP/HTTPS fetching is disabled or the URL is an SSRF risk.""" 

38 from common.settings import get_global_setting 

39 from InvenTree.helpers_model import validate_url_no_ssrf 

40 

41 if not parsed.netloc: 

42 # data: URIs — self-contained, no network access required. 

43 return 

44 

45 if not get_global_setting('REPORT_FETCH_URLS', cache=False): 

46 logger.warning( 

47 "InvenTreeURLFetcher: blocked URL '%s': remote fetching is disabled (REPORT_FETCH_URLS=False)", 

48 url, 

49 ) 

50 raise ValueError( 

51 f'Remote URL fetching is disabled in report templates: {url}' 

52 ) 

53 

54 try: 

55 validate_url_no_ssrf(url) 

56 except ValueError: 

57 logger.warning( 

58 "InvenTreeURLFetcher: blocked URL '%s': resolves to a private or reserved address", 

59 url, 

60 ) 

61 raise