From fa503d2af8b8a7fb7d54580af44dcfa07246a3d5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20NO=C3=8BL?= <tnoel@entrouvert.com>
Date: Fri, 21 Jan 2022 18:01:01 +0100
Subject: [PATCH] misc: allow client side cert on http_requests (#59039)

---
 tests/test_misc.py | 62 ++++++++++++++++++++++++++++++++++++++++++++++
 wcs/qommon/misc.py |  6 +++++
 wcs/settings.py    |  5 ++++
 3 files changed, 73 insertions(+)

diff --git a/tests/test_misc.py b/tests/test_misc.py
index 7d1af9f3..658fa812 100644
--- a/tests/test_misc.py
+++ b/tests/test_misc.py
@@ -20,6 +20,7 @@ from wcs.qommon.form import FileSizeWidget
 from wcs.qommon.http_request import HTTPRequest
 from wcs.qommon.humantime import humanduration2seconds, seconds2humanduration
 from wcs.qommon.misc import (
+    _http_request,
     date_format,
     ellipsize,
     format_time,
@@ -533,3 +534,64 @@ ii  wcs            5.71-1~eob100+1 all          web application to design and se
 def test_uwsgi_spooler_import():
     with pytest.raises(ImportError):
         import wcs.qommon.spooler  # noqa pylint: disable=unused-import
+
+
+@mock.patch('requests.Session.request')
+def test_http_request_global_settings(mock_request):
+    response = {'err': 0, 'data': []}
+    mock_json = mock.Mock(status_code=200)
+    mock_json.json.return_value = response
+    mock_request.return_value = mock_json
+    from django.conf import settings
+
+    response, status, data, auth_header = _http_request('https://example.com/')
+    mock_request.assert_called_once_with(
+        'GET', 'https://example.com/', cert=None, data=None, headers={}, proxies=None, timeout=28
+    )
+    assert status == 200
+
+    settings.REQUESTS_TIMEOUT = 42
+    mock_request.reset_mock()
+    response, status, data, auth_header = _http_request('https://example.com/')
+    mock_request.assert_called_once_with(
+        'GET', 'https://example.com/', cert=None, data=None, headers={}, proxies=None, timeout=42
+    )
+    assert status == 200
+
+    settings.REQUESTS_PROXIES = {
+        'http': 'http://10.10.1.10:3128',
+        'https': 'http://10.10.1.10:1080',
+    }
+    mock_request.reset_mock()
+    response, status, data, auth_header = _http_request('https://example.com/')
+    mock_request.assert_called_once_with(
+        'GET',
+        'https://example.com/',
+        cert=None,
+        data=None,
+        headers={},
+        proxies=settings.REQUESTS_PROXIES,
+        timeout=42,
+    )
+    assert status == 200
+    settings.REQUESTS_PROXIES = None
+
+    settings.REQUESTS_CERT = {
+        'https://example.com/ssl': '/path/client.pem',
+    }
+    mock_request.reset_mock()
+    response, status, data, auth_header = _http_request('https://example.com/ssl/')
+    mock_request.assert_called_once_with(
+        'GET',
+        'https://example.com/ssl/',
+        cert='/path/client.pem',
+        data=None,
+        headers={},
+        proxies=None,
+        timeout=42,
+    )
+    mock_request.reset_mock()
+    response, status, data, auth_header = _http_request('https://example.com/')
+    mock_request.assert_called_once_with(
+        'GET', 'https://example.com/', cert=None, data=None, headers={}, proxies=None, timeout=42
+    )
diff --git a/wcs/qommon/misc.py b/wcs/qommon/misc.py
index 15cf4d33..844cf0a4 100644
--- a/wcs/qommon/misc.py
+++ b/wcs/qommon/misc.py
@@ -359,6 +359,12 @@ def _http_request(
     hostname = splitted_url.netloc
     timeout = timeout or settings.REQUESTS_TIMEOUT
 
+    if cert_file is None:
+        for url_prefix, cert in settings.REQUESTS_CERT.items():
+            if url.startswith(url_prefix):
+                cert_file = cert
+                break
+
     # re-use HTTP adapter to get connection pooling and keep-alive.
     adapter = getattr(get_publisher(), '_http_adapter', None)
     if adapter is None:
diff --git a/wcs/settings.py b/wcs/settings.py
index 8c206484..69612ecc 100644
--- a/wcs/settings.py
+++ b/wcs/settings.py
@@ -187,6 +187,11 @@ REQUESTS_PROXIES = None
 # we use 28s by default: timeout just before web server, which is usually 30s
 REQUESTS_TIMEOUT = 28
 
+# REQUESTS_CERT is a dict of 'url_prefix': cert. cert is used in python-requests call
+# https://docs.python-requests.org/en/master/user/advanced/#client-side-certificates
+# example : REQUESTS_CERT = {'https://example.net/ssl-auth/': '/path/client.pem'}
+REQUESTS_CERT = {}
+
 # For high availability installations with multiple instances of w.c.s.
 # components, one should disable cron jobs execution on secondary servers;
 # set the following variable True disables "cron" management command
-- 
2.34.1

