This is an automated email from the ASF dual-hosted git repository.

oehler pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/streampipes.git


The following commit(s) were added to refs/heads/dev by this push:
     new f455cee118 feat(#3767):  Add jwt authentication to python client 
(#3768)
f455cee118 is described below

commit f455cee1181c9f7376f366a8f1010b1f8f350de9
Author: Sven Oehler <[email protected]>
AuthorDate: Tue Sep 9 17:23:18 2025 +0200

    feat(#3767):  Add jwt authentication to python client (#3768)
    
    * Add jwt credentials
    
    * Add `refresh_headers` method to client
---
 .../streampipes/client/client.py                   |  9 +++-
 .../streampipes/client/credential_provider.py      | 36 +++++++++++++++
 .../tests/client/test_credential_provider.py       | 52 +++++++++++++++++-----
 3 files changed, 85 insertions(+), 12 deletions(-)

diff --git a/streampipes-client-python/streampipes/client/client.py 
b/streampipes-client-python/streampipes/client/client.py
index 374f68569d..3157ce61df 100644
--- a/streampipes-client-python/streampipes/client/client.py
+++ b/streampipes-client-python/streampipes/client/client.py
@@ -136,8 +136,7 @@ class StreamPipesClient:
         # set up a requests session
         # this allows to centrally determine the behavior of all requests made
         self.request_session = Session()
-        self.request_session.headers.update(self.http_headers)
-        
self.request_session.headers.update(self.client_config.additional_headers)
+        self.refresh_headers()
 
         self.logging_level = logging_level
         self._set_up_logging(logging_level=self.logging_level)  # type: ignore
@@ -177,6 +176,12 @@ class StreamPipesClient:
 
         return sp_version
 
+    def refresh_headers(self):
+        """Updates the header of the request session"""
+        self.request_session.headers.clear()
+        self.request_session.headers.update(self.http_headers)
+        
self.request_session.headers.update(self.client_config.additional_headers)
+
     @staticmethod
     def _set_up_logging(logging_level: int) -> None:
         """Configures the logging behavior of the `StreamPipesClient`.
diff --git 
a/streampipes-client-python/streampipes/client/credential_provider.py 
b/streampipes-client-python/streampipes/client/credential_provider.py
index f2fd843b4d..edac71dde8 100644
--- a/streampipes-client-python/streampipes/client/credential_provider.py
+++ b/streampipes-client-python/streampipes/client/credential_provider.py
@@ -30,6 +30,7 @@ from typing import Dict, Optional
 __all__ = [
     "CredentialProvider",
     "StreamPipesApiKeyCredentials",
+    "StreamPipesTokenCredentials",
 ]
 
 from typing_extensions import deprecated
@@ -211,3 +212,38 @@ class StreamPipesApiKeyCredentials(CredentialProvider):
             "X-API-User": user,
             "X-API-Key": token,
         }
+
+
+class StreamPipesTokenCredentials(CredentialProvider):
+    """A credential provider that allows authentication via a JSON Web Token 
(JWT).
+
+    Parameters
+    ----------
+    jwt: str
+        The JSON Web Token to be used for authenticating API requests.
+        This token must include the required claims as issued by StreamPipes.
+    """
+
+    def __init__(self, jwt: str):
+        self.jwt = jwt
+
+    def update_token(self, jwt: str):
+        """Update the stored JWT token.
+
+        Parameters
+        ----------
+        jwt: str
+            The new JSON Web Token to replace the existing one.
+        """
+        self.jwt = jwt
+
+    @property
+    def _authentication_headers(self) -> Dict[str, str]:
+        """Provides the HTTP headers used for authentication with the JWT 
token.
+
+        Returns
+        -------
+        dict
+            A dictionary containing the `Authorization` header with the JWT.
+        """
+        return {"Authorization": self.jwt}
diff --git a/streampipes-client-python/tests/client/test_credential_provider.py 
b/streampipes-client-python/tests/client/test_credential_provider.py
index 57a7b975c4..ef03f259f8 100644
--- a/streampipes-client-python/tests/client/test_credential_provider.py
+++ b/streampipes-client-python/tests/client/test_credential_provider.py
@@ -17,14 +17,15 @@
 import os
 from unittest import TestCase
 
-from streampipes.client.credential_provider import StreamPipesApiKeyCredentials
+from streampipes.client.credential_provider import (
+    StreamPipesApiKeyCredentials,
+    StreamPipesTokenCredentials,
+)
 
 
 class TestStreamPipesApiKeyCredentials(TestCase):
-
     @staticmethod
     def _clear_envs():
-
         if StreamPipesApiKeyCredentials._ENV_KEY_API in os.environ.keys():
             del os.environ[StreamPipesApiKeyCredentials._ENV_KEY_API]
 
@@ -51,7 +52,6 @@ class TestStreamPipesApiKeyCredentials(TestCase):
         self.assertEqual("api-key", credentials.api_key)
 
     def test_pass_credentials_envs_set(self):
-
         os.environ[StreamPipesApiKeyCredentials._ENV_KEY_API] = 
"another-api-key"
         os.environ[StreamPipesApiKeyCredentials._ENV_KEY_USERNAME] = 
"another-user-name"
 
@@ -61,7 +61,6 @@ class TestStreamPipesApiKeyCredentials(TestCase):
         self.assertEqual("api-key", credentials.api_key)
 
     def test_pass_username(self):
-
         self._clear_envs()
         os.environ[StreamPipesApiKeyCredentials._ENV_KEY_API] = 
"another-api-key"
 
@@ -71,14 +70,12 @@ class TestStreamPipesApiKeyCredentials(TestCase):
         self.assertEqual("another-api-key", credentials.api_key)
 
     def test_pass_username_api_key_not_set(self):
-
         self._clear_envs()
 
         with self.assertRaises(AttributeError):
             StreamPipesApiKeyCredentials(username="username")
 
     def test_pass_api_key(self):
-
         self._clear_envs()
         os.environ[StreamPipesApiKeyCredentials._ENV_KEY_USERNAME] = 
"another-username"
 
@@ -88,21 +85,18 @@ class TestStreamPipesApiKeyCredentials(TestCase):
         self.assertEqual("api-key", credentials.api_key)
 
     def test_pass_api_key_username_not_set(self):
-
         self._clear_envs()
 
         with self.assertRaises(AttributeError):
             StreamPipesApiKeyCredentials(api_key="api-key")
 
     def test_nothing_set(self):
-
         self._clear_envs()
 
         with self.assertRaises(AttributeError):
             StreamPipesApiKeyCredentials()
 
     def test_all_from_envs(self):
-
         os.environ[StreamPipesApiKeyCredentials._ENV_KEY_API] = 
"another-api-key"
         os.environ[StreamPipesApiKeyCredentials._ENV_KEY_USERNAME] = 
"another-username"
 
@@ -110,3 +104,41 @@ class TestStreamPipesApiKeyCredentials(TestCase):
 
         self.assertEqual("another-username", credentials.username)
         self.assertEqual("another-api-key", credentials.api_key)
+
+    def test_initialization_sets_jwt(self):
+        token = "Bearer test-token-123"
+        credentials = StreamPipesTokenCredentials(jwt=token)
+
+        self.assertEqual(credentials.jwt, token)
+
+    def test_update_token_changes_jwt(self):
+        token1 = "Bearer old-token"
+        token2 = "Bearer new-token"
+
+        credentials = StreamPipesTokenCredentials(jwt=token1)
+        credentials.update_token(token2)
+
+        self.assertEqual(credentials.jwt, token2)
+        self.assertNotEqual(credentials.jwt, token1)
+
+    def test_authentication_headers_returns_correct_dict(self):
+        token = "Bearer test-token-456"
+        credentials = StreamPipesTokenCredentials(jwt=token)
+
+        headers = credentials._authentication_headers
+
+        self.assertIsInstance(headers, dict)
+        self.assertIn("Authorization", headers)
+        self.assertEqual(headers["Authorization"], token)
+
+    def test_multiple_instances_do_not_share_state(self):
+        token1 = "Bearer token-1"
+        token2 = "Bearer token-2"
+
+        credentials1 = StreamPipesTokenCredentials(jwt=token1)
+        credentials2 = StreamPipesTokenCredentials(jwt=token2)
+
+        self.assertEqual(credentials1.jwt, token1)
+        self.assertEqual(credentials2.jwt, token2)
+        
self.assertEqual(credentials1._authentication_headers["Authorization"], token1)
+        
self.assertEqual(credentials2._authentication_headers["Authorization"], token2)

Reply via email to