From df55100982a2391c2c2b1ba6a1986b02775f98f5 Mon Sep 17 00:00:00 2001 From: Lucy Linder Date: Thu, 21 Mar 2024 17:46:17 +0100 Subject: [PATCH] feat!: rename "ServiceAccount" to "ClientCredentials" and KeycloakAdmin class methods The concept "client credentials" is more suited, as it matches the grant_type and makes it clearer. --- .gitignore | 1 + README.md | 29 +++++++++++++++++------------ mantelo/client.py | 8 ++++---- mantelo/connection.py | 2 +- tests/__init__.py | 0 tests/conftest.py | 4 ++-- tests/test_client.py | 6 +++--- tests/test_connection.py | 12 ++++++------ 8 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 tests/__init__.py diff --git a/.gitignore b/.gitignore index 2259b89..44ed1dd 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ mantelo/version.py .tox/ .mypy/ .venv/ +__pycache__ diff --git a/README.md b/README.md index ed996df..b5b73a9 100644 --- a/README.md +++ b/README.md @@ -63,14 +63,14 @@ Now, assuming you have a Keycloak Server running, what's left is to: 1. authenticate, see [🔐 Authenticate to Keycloak](#-authenticate-to-keycloak) 2. make calls, see [📡 Making calls](#-making-calls) -For a quick test-drive, use the [docker-compose.yml](docker-compose.yml) included +For a quick test drive, use the [docker-compose.yml](docker-compose.yml) included in this repo and start a Keycloak server locally using `docker compose up`. Open a Python REPL and type: ```python from mantelo import KeycloakAdmin -c = KeycloakAdmin.from_credentials( +c = KeycloakAdmin.from_username_password( server_url="http://localhost:9090", realm_name="master", client_id="admin-cli", @@ -98,8 +98,8 @@ setup the admin user with the password `admin`. ## 🔐 Authenticate to Keycloak -To authenticate to Keycloak, you can either use a username+password, or a service account (client -ID+client secret). +To authenticate to Keycloak, you can either use a username+password, or client credentials (client +ID+client secret, also known as service account). The library takes care of fetching a token the first time you need it and keeping it fresh. By default, it tries to use the refresh token (if available) and always guarantees the token is valid @@ -121,7 +121,7 @@ Here is how to connect to the default realm with the admin user and `admin-cli` ```python from mantelo import KeycloakAdmin -client = KeycloakAdmin.from_credentials( +client = KeycloakAdmin.from_username_password( server_url="http://localhost:8080", # base Keycloak URL realm_name="master", # ↓↓ Authentication @@ -138,7 +138,7 @@ you want to query, use the argument `authentication_realm`: ```python from mantelo import KeycloakAdmin -client = KeycloakAdmin.from_credentials( +client = KeycloakAdmin.from_username_password( server_url="http://localhost:8080", # base Keycloak URL realm_name="my-realm", # realm for querying # ↓↓ Authentication @@ -149,7 +149,7 @@ client = KeycloakAdmin.from_credentials( ) ``` -### Authenticating with a service account (client ID + secret) +### Authenticating with client credentials (client ID + secret) To authenticate via a client, the latter needs: @@ -164,7 +164,7 @@ Here is how to connect with a client: ```python from mantelo import KeycloakAdmin -client = KeycloakAdmin.from_service_account( +client = KeycloakAdmin.from_client_credentials( server_url="http://localhost:8080", # base Keycloak URL realm_name="master", # ↓↓ Authentication @@ -180,7 +180,7 @@ you want to query, use the argument `authentication_realm`: ```python from mantelo import KeycloakAdmin -client = KeycloakAdmin.from_service_account( +client = KeycloakAdmin.from_client_credentials( server_url="http://localhost:8080", # base Keycloak URL realm_name="my-realm", # realm for querying # ↓↓ Authentication @@ -202,7 +202,7 @@ an existing token directly (not recommended, as tokens are short-lived): from mantelo.client import BearerAuth, KeycloakAdmin KeycloakAdmin( - server_url="http://localhost:8080" + server_url="http://localhost:8080", realm_name="master", auth=BearerAuth(lambda: "my-token"), ) @@ -213,11 +213,16 @@ If you want to go further, you can create your own `Connection` class (or extend ```python from mantelo.client import BearerAuth, KeycloakAdmin +from mantelo.connection import Connection -connection = Connection(...) +class MyConnection(Connection): + def token(self): + return "" + +connection = MyConnection() KeycloakAdmin( - server_url="http://localhost:8080" + server_url="http://localhost:8080", realm_name="master", auth=BearerAuth(connection.token), ) diff --git a/mantelo/client.py b/mantelo/client.py index cf5bfc7..f88d770 100644 --- a/mantelo/client.py +++ b/mantelo/client.py @@ -6,8 +6,8 @@ from slumber.exceptions import SlumberHttpBaseException from .connection import ( + ClientCredentialsConnection, OpenidConnection, - ServiceAccountConnection, UsernamePasswordConnection, ) from .exceptions import HttpException @@ -85,7 +85,7 @@ def create( ) @classmethod - def from_service_account( + def from_client_credentials( cls, server_url: str, realm_name: str, @@ -94,7 +94,7 @@ def from_service_account( authentication_realm_name: str | None = None, session: requests.Session | None = None, ): - openid_connection = ServiceAccountConnection( + openid_connection = ClientCredentialsConnection( server_url=server_url, realm_name=authentication_realm_name or realm_name, client_id=client_id, @@ -107,7 +107,7 @@ def from_service_account( ) @classmethod - def from_credentials( + def from_username_password( cls, server_url: str, realm_name: str, diff --git a/mantelo/connection.py b/mantelo/connection.py index 806fd5c..bca7d26 100644 --- a/mantelo/connection.py +++ b/mantelo/connection.py @@ -167,7 +167,7 @@ def _token_exchange_data(self) -> dict: @define -class ServiceAccountConnection(OpenidConnection): +class ClientCredentialsConnection(OpenidConnection): client_secret: str def _token_exchange_data(self) -> dict: diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/conftest.py b/tests/conftest.py index 5e74c31..521741b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,6 @@ from mantelo.connection import ( UsernamePasswordConnection, - ServiceAccountConnection, + ClientCredentialsConnection, ) from . import constants import pytest @@ -19,7 +19,7 @@ def openid_connection_password(): @pytest.fixture() def openid_connection_sa(): - return ServiceAccountConnection( + return ClientCredentialsConnection( server_url=constants.TEST_SERVER_URL, realm_name=constants.TEST_REALM, client_id=constants.TEST_CLIENT_ID, diff --git a/tests/test_client.py b/tests/test_client.py index e90c64c..a31a2d2 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -46,7 +46,7 @@ def test_create(openid_connection_sa): def test_password_connection(with_custom_session): session = requests.Session() if with_custom_session else None - adm = KeycloakAdmin.from_credentials( + adm = KeycloakAdmin.from_username_password( server_url=constants.TEST_SERVER_URL, realm_name=constants.TEST_REALM, client_id=constants.ADMIN_CLI_CLIENT, @@ -65,7 +65,7 @@ def test_password_connection(with_custom_session): @pytest.mark.integration def test_client_connection(): - adm = KeycloakAdmin.from_service_account( + adm = KeycloakAdmin.from_client_credentials( server_url=constants.TEST_SERVER_URL, realm_name=constants.TEST_REALM, client_id=constants.TEST_CLIENT_ID, @@ -78,7 +78,7 @@ def test_client_connection(): @pytest.mark.integration def test_different_auth_realm(openid_connection_admin): - adm = KeycloakAdmin.from_credentials( + adm = KeycloakAdmin.from_username_password( server_url=openid_connection_admin.server_url, realm_name=constants.TEST_REALM, client_id=openid_connection_admin.client_id, diff --git a/tests/test_connection.py b/tests/test_connection.py index c0267c3..5f9558b 100644 --- a/tests/test_connection.py +++ b/tests/test_connection.py @@ -4,7 +4,7 @@ from mantelo.connection import ( Token, AuthenticationException, - ServiceAccountConnection, + ClientCredentialsConnection, UsernamePasswordConnection, ) from datetime import datetime, timedelta, timezone @@ -123,8 +123,8 @@ def test_userpasswordconnection_init(): assert conn.refresh_timeout == timedelta(seconds=30) -def test_serviceaccountconnection_init(): - conn = ServiceAccountConnection( +def test_ClientCredentialsconnection_init(): + conn = ClientCredentialsConnection( server_url="https://kc.test", realm_name="test", client_id="foo-client", @@ -153,14 +153,14 @@ def test_openidconnection_default_values(): ) # Defaults - conn = ServiceAccountConnection(**args) + conn = ClientCredentialsConnection(**args) assert isinstance(conn.session, requests.Session) assert conn.refresh_timeout == timedelta(seconds=30) # Override defaults session = requests.Session() timeout = timedelta(seconds=120) - conn = ServiceAccountConnection( + conn = ClientCredentialsConnection( **args, session=session, refresh_timeout=timeout, @@ -169,7 +169,7 @@ def test_openidconnection_default_values(): assert conn.refresh_timeout == timeout # None values fallback to the default values - conn = ServiceAccountConnection( + conn = ClientCredentialsConnection( **args, session=None, refresh_timeout=None,