Browse Source

feat: allow the use of client certificates in all requests (#584)

pull/588/head v4.3.0
Nehuen Gonzalez-Montoro 3 months ago
committed by GitHub
parent
commit
88a8ccc6d3
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 30
      src/keycloak/connection.py
  2. 9
      src/keycloak/keycloak_admin.py
  3. 15
      src/keycloak/keycloak_openid.py
  4. 8
      src/keycloak/openid_connection.py
  5. 10
      tests/test_keycloak_admin.py

30
src/keycloak/connection.py

@ -49,9 +49,13 @@ class ConnectionManager(object):
:type verify: Union[bool,str] :type verify: Union[bool,str]
:param proxies: The proxies servers requests is sent by. :param proxies: The proxies servers requests is sent by.
:type proxies: dict :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. """Init method.
:param base_url: The server URL. :param base_url: The server URL.
@ -65,11 +69,16 @@ class ConnectionManager(object):
:type verify: Union[bool,str] :type verify: Union[bool,str]
:param proxies: The proxies servers requests is sent by. :param proxies: The proxies servers requests is sent by.
:type proxies: dict :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.base_url = base_url
self.headers = headers self.headers = headers
self.timeout = timeout self.timeout = timeout
self.verify = verify self.verify = verify
self.cert = cert
self._s = requests.Session() self._s = requests.Session()
self._s.auth = lambda x: x # don't let requests add auth headers self._s.auth = lambda x: x # don't let requests add auth headers
@ -87,7 +96,7 @@ class ConnectionManager(object):
if proxies: if proxies:
self._s.proxies.update(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.auth = None # don't let requests add auth headers
self.async_s.transport = httpx.AsyncHTTPTransport(retries=1) self.async_s.transport = httpx.AsyncHTTPTransport(retries=1)
@ -140,6 +149,19 @@ class ConnectionManager(object):
def verify(self, value): def verify(self, value):
self._verify = 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 @property
def headers(self): def headers(self):
"""Return header request to the server. """Return header request to the server.
@ -213,6 +235,7 @@ class ConnectionManager(object):
headers=self.headers, headers=self.headers,
timeout=self.timeout, timeout=self.timeout,
verify=self.verify, verify=self.verify,
cert=self.cert,
) )
except Exception as e: except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e) raise KeycloakConnectionError("Can't connect to server (%s)" % e)
@ -238,6 +261,7 @@ class ConnectionManager(object):
headers=self.headers, headers=self.headers,
timeout=self.timeout, timeout=self.timeout,
verify=self.verify, verify=self.verify,
cert=self.cert,
) )
except Exception as e: except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e) raise KeycloakConnectionError("Can't connect to server (%s)" % e)
@ -263,6 +287,7 @@ class ConnectionManager(object):
headers=self.headers, headers=self.headers,
timeout=self.timeout, timeout=self.timeout,
verify=self.verify, verify=self.verify,
cert=self.cert,
) )
except Exception as e: except Exception as e:
raise KeycloakConnectionError("Can't connect to server (%s)" % e) raise KeycloakConnectionError("Can't connect to server (%s)" % e)
@ -288,6 +313,7 @@ class ConnectionManager(object):
headers=self.headers, headers=self.headers,
timeout=self.timeout, timeout=self.timeout,
verify=self.verify, verify=self.verify,
cert=self.cert,
) )
return r return r
except Exception as e: except Exception as e:

9
src/keycloak/keycloak_admin.py

@ -73,6 +73,10 @@ class KeycloakAdmin:
:type user_realm_name: str :type user_realm_name: str
:param timeout: connection timeout in seconds :param timeout: connection timeout in seconds
:type timeout: int :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. :param connection: A KeycloakOpenIDConnection as an alternative to individual params.
:type connection: KeycloakOpenIDConnection :type connection: KeycloakOpenIDConnection
""" """
@ -93,6 +97,7 @@ class KeycloakAdmin:
custom_headers=None, custom_headers=None,
user_realm_name=None, user_realm_name=None,
timeout=60, timeout=60,
cert=None,
connection: Optional[KeycloakOpenIDConnection] = None, connection: Optional[KeycloakOpenIDConnection] = None,
): ):
"""Init method. """Init method.
@ -123,6 +128,9 @@ class KeycloakAdmin:
:type user_realm_name: str :type user_realm_name: str
:param timeout: connection timeout in seconds :param timeout: connection timeout in seconds
:type timeout: int :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. :param connection: An OpenID Connection as an alternative to individual params.
:type connection: KeycloakOpenIDConnection :type connection: KeycloakOpenIDConnection
""" """
@ -139,6 +147,7 @@ class KeycloakAdmin:
user_realm_name=user_realm_name, user_realm_name=user_realm_name,
custom_headers=custom_headers, custom_headers=custom_headers,
timeout=timeout, timeout=timeout,
cert=cert,
) )
@property @property

15
src/keycloak/keycloak_openid.py

@ -74,6 +74,9 @@ class KeycloakOpenID:
:param custom_headers: dict of custom header to pass to each HTML request :param custom_headers: dict of custom header to pass to each HTML request
:param proxies: dict of proxies to sent the request by. :param proxies: dict of proxies to sent the request by.
:param timeout: connection timeout in seconds :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__( def __init__(
@ -86,6 +89,7 @@ class KeycloakOpenID:
custom_headers=None, custom_headers=None,
proxies=None, proxies=None,
timeout=60, timeout=60,
cert=None,
): ):
"""Init method. """Init method.
@ -106,13 +110,22 @@ class KeycloakOpenID:
:type proxies: dict :type proxies: dict
:param timeout: connection timeout in seconds :param timeout: connection timeout in seconds
:type timeout: int :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_id = client_id
self.client_secret_key = client_secret_key self.client_secret_key = client_secret_key
self.realm_name = realm_name self.realm_name = realm_name
headers = custom_headers if custom_headers is not None else dict() headers = custom_headers if custom_headers is not None else dict()
self.connection = ConnectionManager( 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() self.authorization = Authorization()

8
src/keycloak/openid_connection.py

@ -70,6 +70,7 @@ class KeycloakOpenIDConnection(ConnectionManager):
custom_headers=None, custom_headers=None,
user_realm_name=None, user_realm_name=None,
timeout=60, timeout=60,
cert=None,
): ):
"""Init method. """Init method.
@ -99,6 +100,10 @@ class KeycloakOpenIDConnection(ConnectionManager):
:type user_realm_name: str :type user_realm_name: str
:param timeout: connection timeout in seconds :param timeout: connection timeout in seconds
:type timeout: int :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 # token is renewed when it hits 90% of its lifetime. This is to account for any possible
# clock skew. # clock skew.
@ -117,12 +122,14 @@ class KeycloakOpenIDConnection(ConnectionManager):
self.timeout = timeout self.timeout = timeout
self.custom_headers = custom_headers self.custom_headers = custom_headers
self.headers = {**self.headers, "Content-Type": "application/json"} self.headers = {**self.headers, "Content-Type": "application/json"}
self.cert = cert
super().__init__( super().__init__(
base_url=self.server_url, base_url=self.server_url,
headers=self.headers, headers=self.headers,
timeout=self.timeout, timeout=self.timeout,
verify=self.verify, verify=self.verify,
cert=cert,
) )
@property @property
@ -297,6 +304,7 @@ class KeycloakOpenIDConnection(ConnectionManager):
client_secret_key=self.client_secret_key, client_secret_key=self.client_secret_key,
timeout=self.timeout, timeout=self.timeout,
custom_headers=self.custom_headers, custom_headers=self.custom_headers,
cert=self.cert,
) )
return self._keycloak_openid return self._keycloak_openid

10
tests/test_keycloak_admin.py

@ -5198,10 +5198,7 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str):
mock_put.assert_awaited_once_with( mock_put.assert_awaited_once_with(
ANY, ANY,
data='["UPDATE_PASSWORD"]', 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, headers=ANY,
timeout=60, timeout=60,
) )
@ -5216,10 +5213,7 @@ async def test_a_email_query_param_handling(admin: KeycloakAdmin, user: str):
mock_put.assert_awaited_once_with( mock_put.assert_awaited_once_with(
ANY, ANY,
data=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, headers=ANY,
timeout=60, timeout=60,
) )

Loading…
Cancel
Save