Skip to content

Commit

Permalink
feat: allow the use of client certificates in all requests (#584)
Browse files Browse the repository at this point in the history
  • Loading branch information
nehuengonzalez authored Aug 1, 2024
1 parent 2091d59 commit 88a8ccc
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 11 deletions.
30 changes: 28 additions & 2 deletions src/keycloak/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,13 @@ class ConnectionManager(object):
:type verify: Union[bool,str]
:param proxies: The proxies servers requests is sent by.
:type proxies: dict
:param cert: An SSL certificate used by the requested host to authenticate the client.
Either a path to an SSL certificate file, or two-tuple of
(certificate file, key file).
:type cert: Union[str,Tuple[str,str]]
"""

def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None):
def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None, cert=None):
"""Init method.
:param base_url: The server URL.
Expand All @@ -65,11 +69,16 @@ def __init__(self, base_url, headers={}, timeout=60, verify=True, proxies=None):
:type verify: Union[bool,str]
:param proxies: The proxies servers requests is sent by.
:type proxies: dict
:param cert: An SSL certificate used by the requested host to authenticate the client.
Either a path to an SSL certificate file, or two-tuple of
(certificate file, key file).
:type cert: Union[str,Tuple[str,str]]
"""
self.base_url = base_url
self.headers = headers
self.timeout = timeout
self.verify = verify
self.cert = cert
self._s = requests.Session()
self._s.auth = lambda x: x # don't let requests add auth headers

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

self.async_s = httpx.AsyncClient(verify=verify, proxies=proxies)
self.async_s = httpx.AsyncClient(verify=verify, proxies=proxies, cert=cert)
self.async_s.auth = None # don't let requests add auth headers
self.async_s.transport = httpx.AsyncHTTPTransport(retries=1)

Expand Down Expand Up @@ -140,6 +149,19 @@ def verify(self):
def verify(self, value):
self._verify = value

@property
def cert(self):
"""Return client certificates in use for request to the server.
:returns: Client certificate
:rtype: Union[str,Tuple[str,str]]
"""
return self._cert

@cert.setter
def cert(self, value):
self._cert = value

@property
def headers(self):
"""Return header request to the server.
Expand Down Expand Up @@ -213,6 +235,7 @@ def raw_get(self, path, **kwargs):
headers=self.headers,
timeout=self.timeout,
verify=self.verify,
cert=self.cert,
)
except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
Expand All @@ -238,6 +261,7 @@ def raw_post(self, path, data, **kwargs):
headers=self.headers,
timeout=self.timeout,
verify=self.verify,
cert=self.cert,
)
except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
Expand All @@ -263,6 +287,7 @@ def raw_put(self, path, data, **kwargs):
headers=self.headers,
timeout=self.timeout,
verify=self.verify,
cert=self.cert,
)
except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e)
Expand All @@ -288,6 +313,7 @@ def raw_delete(self, path, data=None, **kwargs):
headers=self.headers,
timeout=self.timeout,
verify=self.verify,
cert=self.cert,
)
return r
except Exception as e:
Expand Down
9 changes: 9 additions & 0 deletions src/keycloak/keycloak_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,10 @@ class KeycloakAdmin:
:type user_realm_name: str
:param timeout: connection timeout in seconds
:type timeout: int
:param cert: An SSL certificate used by the requested host to authenticate the client.
Either a path to an SSL certificate file, or two-tuple of
(certificate file, key file).
:type cert: Union[str,Tuple[str,str]]
:param connection: A KeycloakOpenIDConnection as an alternative to individual params.
:type connection: KeycloakOpenIDConnection
"""
Expand All @@ -93,6 +97,7 @@ def __init__(
custom_headers=None,
user_realm_name=None,
timeout=60,
cert=None,
connection: Optional[KeycloakOpenIDConnection] = None,
):
"""Init method.
Expand Down Expand Up @@ -123,6 +128,9 @@ def __init__(
:type user_realm_name: str
:param timeout: connection timeout in seconds
:type timeout: int
:param cert: An SSL certificate used by the requested host to authenticate the client.
Either a path to an SSL certificate file, or two-tuple of (certificate file, key file).
:type cert: Union[str,Tuple[str,str]]
:param connection: An OpenID Connection as an alternative to individual params.
:type connection: KeycloakOpenIDConnection
"""
Expand All @@ -139,6 +147,7 @@ def __init__(
user_realm_name=user_realm_name,
custom_headers=custom_headers,
timeout=timeout,
cert=cert,
)

@property
Expand Down
15 changes: 14 additions & 1 deletion src/keycloak/keycloak_openid.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ class KeycloakOpenID:
:param custom_headers: dict of custom header to pass to each HTML request
:param proxies: dict of proxies to sent the request by.
:param timeout: connection timeout in seconds
:param cert: An SSL certificate used by the requested host to authenticate the client.
Either a path to an SSL certificate file, or two-tuple of
(certificate file, key file).
"""

def __init__(
Expand All @@ -86,6 +89,7 @@ def __init__(
custom_headers=None,
proxies=None,
timeout=60,
cert=None,
):
"""Init method.
Expand All @@ -106,13 +110,22 @@ def __init__(
:type proxies: dict
:param timeout: connection timeout in seconds
:type timeout: int
:param cert: An SSL certificate used by the requested host to authenticate the client.
Either a path to an SSL certificate file, or two-tuple of
(certificate file, key file).
:type cert: Union[str,Tuple[str,str]]
"""
self.client_id = client_id
self.client_secret_key = client_secret_key
self.realm_name = realm_name
headers = custom_headers if custom_headers is not None else dict()
self.connection = ConnectionManager(
base_url=server_url, headers=headers, timeout=timeout, verify=verify, proxies=proxies
base_url=server_url,
headers=headers,
timeout=timeout,
verify=verify,
proxies=proxies,
cert=cert,
)

self.authorization = Authorization()
Expand Down
8 changes: 8 additions & 0 deletions src/keycloak/openid_connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def __init__(
custom_headers=None,
user_realm_name=None,
timeout=60,
cert=None,
):
"""Init method.
Expand Down Expand Up @@ -99,6 +100,10 @@ def __init__(
:type user_realm_name: str
:param timeout: connection timeout in seconds
:type timeout: int
:param cert: An SSL certificate used by the requested host to authenticate the client.
Either a path to an SSL certificate file, or two-tuple of
(certificate file, key file).
:type cert: Union[str,Tuple[str,str]]
"""
# token is renewed when it hits 90% of its lifetime. This is to account for any possible
# clock skew.
Expand All @@ -117,12 +122,14 @@ def __init__(
self.timeout = timeout
self.custom_headers = custom_headers
self.headers = {**self.headers, "Content-Type": "application/json"}
self.cert = cert

super().__init__(
base_url=self.server_url,
headers=self.headers,
timeout=self.timeout,
verify=self.verify,
cert=cert,
)

@property
Expand Down Expand Up @@ -297,6 +304,7 @@ def keycloak_openid(self) -> KeycloakOpenID:
client_secret_key=self.client_secret_key,
timeout=self.timeout,
custom_headers=self.custom_headers,
cert=self.cert,
)

return self._keycloak_openid
Expand Down
10 changes: 2 additions & 8 deletions tests/test_keycloak_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -5198,10 +5198,7 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str):
mock_put.assert_awaited_once_with(
ANY,
data='["UPDATE_PASSWORD"]',
params={
"client_id": "update-account-client-id",
"redirect_uri": "https://example.com",
},
params={"client_id": "update-account-client-id", "redirect_uri": "https://example.com"},
headers=ANY,
timeout=60,
)
Expand All @@ -5216,10 +5213,7 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str):
mock_put.assert_awaited_once_with(
ANY,
data=ANY,
params={
"client_id": "verify-client-id",
"redirect_uri": "https://example.com",
},
params={"client_id": "verify-client-id", "redirect_uri": "https://example.com"},
headers=ANY,
timeout=60,
)
Expand Down

0 comments on commit 88a8ccc

Please sign in to comment.