From fa523d66b63a2a3bc02f78957e37d9b2078a8baf Mon Sep 17 00:00:00 2001
From: "Antoine R. Dumont (@ardumont)" <ardumont@softwareheritage.org>
Date: Fri, 5 Mar 2021 14:11:13 +0100
Subject: [PATCH] keycloak: Open from_config* method to instantiate
 KeycloakOpenIDConnect

Related to T3079
---
 swh/auth/keycloak.py            | 42 ++++++++++++++++++++++
 swh/auth/tests/test_keycloak.py | 64 +++++++++++++++++++++++++++++++++
 2 files changed, 106 insertions(+)

diff --git a/swh/auth/keycloak.py b/swh/auth/keycloak.py
index 72d243c..4d72d63 100644
--- a/swh/auth/keycloak.py
+++ b/swh/auth/keycloak.py
@@ -8,6 +8,8 @@ from urllib.parse import urlencode
 
 from keycloak import KeycloakOpenID
 
+from swh.core.config import load_from_envvar
+
 
 class KeycloakOpenIDConnect:
     """
@@ -158,3 +160,43 @@ class KeycloakOpenIDConnect:
             A dictionary fillled with user information
         """
         return self._keycloak.userinfo(access_token)
+
+    @classmethod
+    def from_config(cls, **kwargs: Any) -> "KeycloakOpenIDConnect":
+        """Instantiate a KeycloakOpenIDConnect class from a configuration dict.
+
+        Args:
+
+            kwargs: configuration dict for the instance, with one keycloak key, whose
+                value is a Dict with the following keys:
+                - server_url: URL of the Keycloak server
+                - realm_name: The realm name
+                - client_id: The OpenID Connect client identifier
+
+        Returns:
+            the KeycloakOpenIDConnect instance
+
+        """
+        cfg = kwargs["keycloak"]
+        return cls(
+            server_url=cfg["server_url"],
+            realm_name=cfg["realm_name"],
+            client_id=cfg["client_id"],
+        )
+
+    @classmethod
+    def from_configfile(cls, **kwargs: Any) -> "KeycloakOpenIDConnect":
+
+        """Instantiate a KeycloakOpenIDConnect class from the configuration loaded from the
+        SWH_CONFIG_FILENAME envvar, with potential extra keyword arguments if their
+        value is not None.
+
+        Args:
+            kwargs: kwargs passed to instantiation call
+
+        Returns:
+            the KeycloakOpenIDConnect instance
+        """
+        config = dict(load_from_envvar()).get("keycloak", {})
+        config.update({k: v for k, v in kwargs.items() if v is not None})
+        return cls.from_config(keycloak=config)
diff --git a/swh/auth/tests/test_keycloak.py b/swh/auth/tests/test_keycloak.py
index 5cf774e..3bcda9f 100644
--- a/swh/auth/tests/test_keycloak.py
+++ b/swh/auth/tests/test_keycloak.py
@@ -4,11 +4,14 @@
 # See top-level LICENSE file for more information
 
 from copy import copy
+import os
 from urllib.parse import parse_qs, urlparse
 
 from keycloak.exceptions import KeycloakError
 import pytest
+import yaml
 
+from swh.auth.keycloak import KeycloakOpenIDConnect
 from swh.auth.pytest_plugin import keycloak_mock_factory
 from swh.auth.tests.sample_data import (
     CLIENT_ID,
@@ -18,6 +21,7 @@ from swh.auth.tests.sample_data import (
     SERVER_URL,
     USER_INFO,
 )
+from swh.core.config import read
 
 # Make keycloak fixture to use for tests below.
 keycloak_mock = keycloak_mock_factory(
@@ -98,3 +102,63 @@ def test_keycloak_decode_token(keycloak_mock):
 def test_keycloak_login(keycloak_mock):
     actual_response = keycloak_mock.login("username", "password")
     assert actual_response == OIDC_PROFILE
+
+
+@pytest.fixture
+def auth_config():
+    return {
+        "keycloak": {
+            "server_url": "https://auth.swh.org/SWHTest",
+            "realm_name": "SWHTest",
+            "client_id": "client_id",
+        }
+    }
+
+
+@pytest.fixture
+def auth_config_path(tmp_path, monkeypatch, auth_config):
+    conf_path = os.path.join(tmp_path, "auth.yml")
+    with open(conf_path, "w") as f:
+        f.write(yaml.dump(auth_config))
+    monkeypatch.setenv("SWH_CONFIG_FILENAME", conf_path)
+    return conf_path
+
+
+def test_auth_KeycloakOpenIDConnect_from_config(auth_config):
+    """Instantiating keycloak client out of configuration dict is possible
+
+     """
+    client = KeycloakOpenIDConnect.from_config(**auth_config)
+
+    assert client.server_url == auth_config["keycloak"]["server_url"]
+    assert client.realm_name == auth_config["keycloak"]["realm_name"]
+    assert client.client_id == auth_config["keycloak"]["client_id"]
+
+
+def test_auth_KeycloakOpenIDConnect_from_configfile(auth_config_path, monkeypatch):
+    """Instantiating keycloak client out of environment variable is possible
+
+     """
+    client = KeycloakOpenIDConnect.from_configfile()
+
+    auth_config = read(auth_config_path)
+
+    assert client.server_url == auth_config["keycloak"]["server_url"]
+    assert client.realm_name == auth_config["keycloak"]["realm_name"]
+    assert client.client_id == auth_config["keycloak"]["client_id"]
+
+
+def test_auth_KeycloakOpenIDConnect_from_configfile_override(
+    auth_config_path, monkeypatch
+):
+    """Instantiating keycloak client out of environment variable is possible
+    And caller can override the configuration  at calling
+
+     """
+    client = KeycloakOpenIDConnect.from_configfile(client_id="foobar")
+
+    auth_config = read(auth_config_path)
+
+    assert client.server_url == auth_config["keycloak"]["server_url"]
+    assert client.realm_name == auth_config["keycloak"]["realm_name"]
+    assert client.client_id == "foobar"
-- 
GitLab