Skip to content

Commit 88a8ccc

Browse files
feat: allow the use of client certificates in all requests (#584)
1 parent 2091d59 commit 88a8ccc

File tree

5 files changed

+61
-11
lines changed

5 files changed

+61
-11
lines changed

src/keycloak/connection.py

+28-2
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,13 @@ class ConnectionManager(object):
4949
:type verify: Union[bool,str]
5050
:param proxies: The proxies servers requests is sent by.
5151
:type proxies: dict
52+
:param cert: An SSL certificate used by the requested host to authenticate the client.
53+
Either a path to an SSL certificate file, or two-tuple of
54+
(certificate file, key file).
55+
:type cert: Union[str,Tuple[str,str]]
5256
"""
5357

54-
def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None):
58+
def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None, cert=None):
5559
"""Init method.
5660
5761
:param base_url: The server URL.
@@ -65,11 +69,16 @@ def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None):
6569
:type verify: Union[bool,str]
6670
:param proxies: The proxies servers requests is sent by.
6771
:type proxies: dict
72+
:param cert: An SSL certificate used by the requested host to authenticate the client.
73+
Either a path to an SSL certificate file, or two-tuple of
74+
(certificate file, key file).
75+
:type cert: Union[str,Tuple[str,str]]
6876
"""
6977
self.base_url = base_url
7078
self.headers = headers
7179
self.timeout = timeout
7280
self.verify = verify
81+
self.cert = cert
7382
self._s = requests.Session()
7483
self._s.auth = lambda x: x # don't let requests add auth headers
7584

@@ -87,7 +96,7 @@ def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None):
8796
if proxies:
8897
self._s.proxies.update(proxies)
8998

90-
self.async_s = httpx.AsyncClient(verify=verify, proxies=proxies)
99+
self.async_s = httpx.AsyncClient(verify=verify, proxies=proxies, cert=cert)
91100
self.async_s.auth = None # don't let requests add auth headers
92101
self.async_s.transport = httpx.AsyncHTTPTransport(retries=1)
93102

@@ -140,6 +149,19 @@ def verify(self):
140149
def verify(self, value):
141150
self._verify = value
142151

152+
@property
153+
def cert(self):
154+
"""Return client certificates in use for request to the server.
155+
156+
:returns: Client certificate
157+
:rtype: Union[str,Tuple[str,str]]
158+
"""
159+
return self._cert
160+
161+
@cert.setter
162+
def cert(self, value):
163+
self._cert = value
164+
143165
@property
144166
def headers(self):
145167
"""Return header request to the server.
@@ -213,6 +235,7 @@ def raw_get(self, path, **kwargs):
213235
headers=self.headers,
214236
timeout=self.timeout,
215237
verify=self.verify,
238+
cert=self.cert,
216239
)
217240
except Exception as e:
218241
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
@@ -238,6 +261,7 @@ def raw_post(self, path, data, **kwargs):
238261
headers=self.headers,
239262
timeout=self.timeout,
240263
verify=self.verify,
264+
cert=self.cert,
241265
)
242266
except Exception as e:
243267
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
@@ -263,6 +287,7 @@ def raw_put(self, path, data, **kwargs):
263287
headers=self.headers,
264288
timeout=self.timeout,
265289
verify=self.verify,
290+
cert=self.cert,
266291
)
267292
except Exception as e:
268293
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
@@ -288,6 +313,7 @@ def raw_delete(self, path, data=None, **kwargs):
288313
headers=self.headers,
289314
timeout=self.timeout,
290315
verify=self.verify,
316+
cert=self.cert,
291317
)
292318
return r
293319
except Exception as e:

src/keycloak/keycloak_admin.py

+9
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ class KeycloakAdmin:
7373
:type user_realm_name: str
7474
:param timeout: connection timeout in seconds
7575
:type timeout: int
76+
:param cert: An SSL certificate used by the requested host to authenticate the client.
77+
Either a path to an SSL certificate file, or two-tuple of
78+
(certificate file, key file).
79+
:type cert: Union[str,Tuple[str,str]]
7680
:param connection: A KeycloakOpenIDConnection as an alternative to individual params.
7781
:type connection: KeycloakOpenIDConnection
7882
"""
@@ -93,6 +97,7 @@ def __init__(
9397
custom_headers=None,
9498
user_realm_name=None,
9599
timeout=60,
100+
cert=None,
96101
connection: Optional[KeycloakOpenIDConnection] = None,
97102
):
98103
"""Init method.
@@ -123,6 +128,9 @@ def __init__(
123128
:type user_realm_name: str
124129
:param timeout: connection timeout in seconds
125130
:type timeout: int
131+
:param cert: An SSL certificate used by the requested host to authenticate the client.
132+
Either a path to an SSL certificate file, or two-tuple of (certificate file, key file).
133+
:type cert: Union[str,Tuple[str,str]]
126134
:param connection: An OpenID Connection as an alternative to individual params.
127135
:type connection: KeycloakOpenIDConnection
128136
"""
@@ -139,6 +147,7 @@ def __init__(
139147
user_realm_name=user_realm_name,
140148
custom_headers=custom_headers,
141149
timeout=timeout,
150+
cert=cert,
142151
)
143152

144153
@property

src/keycloak/keycloak_openid.py

+14-1
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ class KeycloakOpenID:
7474
:param custom_headers: dict of custom header to pass to each HTML request
7575
:param proxies: dict of proxies to sent the request by.
7676
:param timeout: connection timeout in seconds
77+
:param cert: An SSL certificate used by the requested host to authenticate the client.
78+
Either a path to an SSL certificate file, or two-tuple of
79+
(certificate file, key file).
7780
"""
7881

7982
def __init__(
@@ -86,6 +89,7 @@ def __init__(
8689
custom_headers=None,
8790
proxies=None,
8891
timeout=60,
92+
cert=None,
8993
):
9094
"""Init method.
9195
@@ -106,13 +110,22 @@ def __init__(
106110
:type proxies: dict
107111
:param timeout: connection timeout in seconds
108112
:type timeout: int
113+
:param cert: An SSL certificate used by the requested host to authenticate the client.
114+
Either a path to an SSL certificate file, or two-tuple of
115+
(certificate file, key file).
116+
:type cert: Union[str,Tuple[str,str]]
109117
"""
110118
self.client_id = client_id
111119
self.client_secret_key = client_secret_key
112120
self.realm_name = realm_name
113121
headers = custom_headers if custom_headers is not None else dict()
114122
self.connection = ConnectionManager(
115-
base_url=server_url, headers=headers, timeout=timeout, verify=verify, proxies=proxies
123+
base_url=server_url,
124+
headers=headers,
125+
timeout=timeout,
126+
verify=verify,
127+
proxies=proxies,
128+
cert=cert,
116129
)
117130

118131
self.authorization = Authorization()

src/keycloak/openid_connection.py

+8
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def __init__(
7070
custom_headers=None,
7171
user_realm_name=None,
7272
timeout=60,
73+
cert=None,
7374
):
7475
"""Init method.
7576
@@ -99,6 +100,10 @@ def __init__(
99100
:type user_realm_name: str
100101
:param timeout: connection timeout in seconds
101102
:type timeout: int
103+
:param cert: An SSL certificate used by the requested host to authenticate the client.
104+
Either a path to an SSL certificate file, or two-tuple of
105+
(certificate file, key file).
106+
:type cert: Union[str,Tuple[str,str]]
102107
"""
103108
# token is renewed when it hits 90% of its lifetime. This is to account for any possible
104109
# clock skew.
@@ -117,12 +122,14 @@ def __init__(
117122
self.timeout = timeout
118123
self.custom_headers = custom_headers
119124
self.headers = {**self.headers, "Content-Type": "application/json"}
125+
self.cert = cert
120126

121127
super().__init__(
122128
base_url=self.server_url,
123129
headers=self.headers,
124130
timeout=self.timeout,
125131
verify=self.verify,
132+
cert=cert,
126133
)
127134

128135
@property
@@ -297,6 +304,7 @@ def keycloak_openid(self) -> KeycloakOpenID:
297304
client_secret_key=self.client_secret_key,
298305
timeout=self.timeout,
299306
custom_headers=self.custom_headers,
307+
cert=self.cert,
300308
)
301309

302310
return self._keycloak_openid

tests/test_keycloak_admin.py

+2-8
Original file line numberDiff line numberDiff line change
@@ -5198,10 +5198,7 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str):
51985198
mock_put.assert_awaited_once_with(
51995199
ANY,
52005200
data='["UPDATE_PASSWORD"]',
5201-
params={
5202-
"client_id": "update-account-client-id",
5203-
"redirect_uri": "https://example.com",
5204-
},
5201+
params={"client_id": "update-account-client-id", "redirect_uri": "https://example.com"},
52055202
headers=ANY,
52065203
timeout=60,
52075204
)
@@ -5216,10 +5213,7 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str):
52165213
mock_put.assert_awaited_once_with(
52175214
ANY,
52185215
data=ANY,
5219-
params={
5220-
"client_id": "verify-client-id",
5221-
"redirect_uri": "https://example.com",
5222-
},
5216+
params={"client_id": "verify-client-id", "redirect_uri": "https://example.com"},
52235217
headers=ANY,
52245218
timeout=60,
52255219
)

0 commit comments

Comments
 (0)