From 90c37d3b60b37fcd192b2d882188924087c720a7 Mon Sep 17 00:00:00 2001 From: BarCo Date: Tue, 22 Jul 2025 18:01:29 +0300 Subject: [PATCH 1/7] Support jst token usage instead of timbr token (with backward compatability) --- pytimbr_api/timbr_http_connector.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pytimbr_api/timbr_http_connector.py b/pytimbr_api/timbr_http_connector.py index ca8a396..7956c95 100644 --- a/pytimbr_api/timbr_http_connector.py +++ b/pytimbr_api/timbr_http_connector.py @@ -33,6 +33,8 @@ def run_query( nested: str = 'false', verify_ssl: bool = True, enable_IPv6: bool = False, + is_jwt: bool = False, + jwt_tenant_id: str = None, ): datasource_addition = '' if datasource: @@ -44,11 +46,17 @@ def run_query( headers = { 'Content-Type': 'application/text', - 'x-api-key': token, 'nested': nested, 'Connection': 'close', } - + + if is_jwt: + headers['x-jwt-token'] = token + if jwt_tenant_id: + headers['x-jwt-tenant-id'] = jwt_tenant_id + else: + headers['x-api-key'] = token + requests.packages.urllib3.util.connection.HAS_IPV6 = enable_IPv6 response = requests.post( f'{base_url}timbr/openapi/ontology/{ontology}/query{datasource_addition}', From ed0cce2a95e7f477fb509711f953ed37df0ba886 Mon Sep 17 00:00:00 2001 From: BarCo Date: Tue, 22 Jul 2025 18:02:01 +0300 Subject: [PATCH 2/7] Add sample usage of jwt token --- examples/example.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/examples/example.py b/examples/example.py index bada768..3e28548 100644 --- a/examples/example.py +++ b/examples/example.py @@ -18,12 +18,14 @@ # url - Required - String - The IP / Hostname of the Timbr platform. # ontology - Required - String - The ontology / knowledge graph to connect to. - # token - Required - String - Timbr token value. + # token - Required - String - Timbr token value or JWT token value. Note: If you are using JWT token, you need to set the is_jwt parameter to True. # query - Required - String - The query that you want to execute. # datasource - Optional - String - Add the specific datasource name that you want to query from, the default value is the current active datasource of your ontology. # nested - Optional - String - Change to 'true' if nested flag needs to be enabled. make sure this flag contains string value not bool value. # verify_ssl - Optional - Boolean - Verifying the target server's SSL Certificate, use False to disable this process. # enable_IPv6 - Optional - Boolean - Change to 'true' if you are using IPv6 connection. + # is_jwt - Optional - Boolean - Set to True if you are using JWT token, otherwise set to False. + # jwt_tenant_id - Optional - String - The tenant ID for JWT authentication # HTTP example response = timbr.run_query( @@ -39,6 +41,24 @@ print(response) + +# Example for JWT token usage +response = timbr.run_query( + url = "https://mytimbrenv.com:443", + ontology = "my_ontology", + token = "my_jwt_token", + query = "SELECT * FROM timbr.sys_concepts", + datasource = "my_datasource", + nested = "false", + verify_ssl = True, + enable_IPv6 = False, + is_jwt = True, + jwt_tenant_id = "my_tenant_id", +) + +print(response) + + # HTTPS example response = timbr.simpleQueryExecution( url = "https://mytimbrenv.com:443", From a3b98fc4c6216f8de83c805f3a6c30f50e89e144 Mon Sep 17 00:00:00 2001 From: BarCo Date: Wed, 23 Jul 2025 10:37:13 +0300 Subject: [PATCH 3/7] Add test case for JWT token authorization --- test/conftest.py | 6 ++++++ test/test_jwt_token.py | 45 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 test/test_jwt_token.py diff --git a/test/conftest.py b/test/conftest.py index 8a4c2ef..65974bb 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -31,4 +31,10 @@ def test_config(): "verify_ssl": convert_env_to_bool(os.getenv("VERIFY_SSL")), "nested": os.getenv("NESTED"), "enableIPv6": convert_env_to_bool(os.getenv("ENABLE_IPV6")), + "jwt_tenant_id": os.getenv("JWT_TENANT_ID"), + "jwt_client_id": os.getenv("JWT_CLIENT_ID"), + "jwt_username": os.getenv("JWT_USERNAME"), + "jwt_password": os.getenv("JWT_PASSWORD"), + "jwt_scope": os.getenv("JWT_SCOPE"), + "jwt_secret": os.getenv("JWT_SECRET"), } diff --git a/test/test_jwt_token.py b/test/test_jwt_token.py new file mode 100644 index 0000000..99d73fe --- /dev/null +++ b/test/test_jwt_token.py @@ -0,0 +1,45 @@ +import requests +from pytimbr_api.timbr_http_connector import run_query + +def test_query_using_jwt(test_config): + # Azure AD token endpoint URL + token_url = f'https://login.microsoftonline.com/{test_config["jwt_tenant_id"]}/oauth2/v2.0/token' + + # Request payload for token exchange + payload = { + 'client_id': test_config["jwt_client_id"], + 'client_secret': test_config["jwt_secret"], + 'scope': test_config["jwt_scope"], + 'username': test_config["jwt_username"], + 'password': test_config["jwt_password"], + 'grant_type': 'password' + } + + # Request headers + headers = { + 'Content-Type': 'application/x-www-form-urlencoded' + } + + # Make the request to get the access token + response = requests.post(token_url, data=payload, headers=headers) + tokens = response.json() + + if response.status_code == 200: + access_token = tokens.get('access_token') + print(f"Access Token: {access_token}") + else: + print(f"Error fetching access token: {tokens}") + + results = run_query( + url=test_config['url'], + ontology=test_config['ontology'], + token=access_token, + query='SELECT 1', + datasource=test_config['datasource'], + nested='false', + verify_ssl=test_config['verify_ssl'], + enable_IPv6=test_config['enableIPv6'], + is_jwt=True, + ) + + assert results is not None, "Results should not be None" From 161be57b112f2c7af2945cf47685c7169730178e Mon Sep 17 00:00:00 2001 From: BarCo Date: Wed, 23 Jul 2025 10:43:47 +0300 Subject: [PATCH 4/7] Fix test problem --- test/test_jwt_token.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/test_jwt_token.py b/test/test_jwt_token.py index 99d73fe..1836efe 100644 --- a/test/test_jwt_token.py +++ b/test/test_jwt_token.py @@ -24,6 +24,7 @@ def test_query_using_jwt(test_config): response = requests.post(token_url, data=payload, headers=headers) tokens = response.json() + access_token = None if response.status_code == 200: access_token = tokens.get('access_token') print(f"Access Token: {access_token}") From 73f3737ce5aeeebd14861143ddc77ae2c6ca4974 Mon Sep 17 00:00:00 2001 From: BarCo Date: Wed, 23 Jul 2025 10:51:50 +0300 Subject: [PATCH 5/7] Add error message and stop test when failed to get access token --- test/test_jwt_token.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_jwt_token.py b/test/test_jwt_token.py index 1836efe..6b10ae3 100644 --- a/test/test_jwt_token.py +++ b/test/test_jwt_token.py @@ -30,7 +30,8 @@ def test_query_using_jwt(test_config): print(f"Access Token: {access_token}") else: print(f"Error fetching access token: {tokens}") - + assert False, f"Error fetching access token: {tokens}" + results = run_query( url=test_config['url'], ontology=test_config['ontology'], From 9d7d370360fedf6145063a745ef7ee9ce0dcb1af Mon Sep 17 00:00:00 2001 From: BarCo Date: Wed, 23 Jul 2025 10:56:21 +0300 Subject: [PATCH 6/7] Fix github actions env variables --- .github/workflows/install-dependencies-and-run-tests.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/install-dependencies-and-run-tests.yml b/.github/workflows/install-dependencies-and-run-tests.yml index 50f2bfa..a5fd472 100644 --- a/.github/workflows/install-dependencies-and-run-tests.yml +++ b/.github/workflows/install-dependencies-and-run-tests.yml @@ -35,12 +35,18 @@ jobs: ONTOLOGY: ${{ secrets.ONTOLOGY }} TOKEN: ${{ secrets.TOKEN }} QUERY: ${{ secrets.QUERY }} - DATASTORE: ${{ secrets.DATASTORE }} + DATASOURCE: ${{ secrets.DATASOURCE }} HOSTNAME: ${{ secrets.HOSTNAME }} PORT: ${{ secrets.PORT }} ENABLED_SSL: ${{ secrets.ENABLED_SSL }} VERIFY_SSL: ${{ secrets.VERIFY_SSL }} NESTED: ${{ secrets.NESTED }} ENABLE_IPV6: ${{ secrets.ENABLE_IPV6 }} + JWT_TENANT_ID: ${{ secrets.JWT_TENANT_ID }} + JWT_CLIENT_ID: ${{ secrets.JWT_CLIENT_ID }} + JWT_USERNAME: ${{ secrets.JWT_USERNAME }} + JWT_PASSWORD: ${{ secrets.JWT_PASSWORD }} + JWT_SCOPE: ${{ secrets.JWT_SCOPE }} + JWT_SECRET: ${{ secrets.JWT_SECRET }} run: | pytest From ca29ad42497fbfae1c011fb40d4178ee118c3f61 Mon Sep 17 00:00:00 2001 From: Dor Eliyahu <80757492+DorEliyahu176@users.noreply.github.com> Date: Wed, 23 Jul 2025 10:59:10 +0300 Subject: [PATCH 7/7] Update example.py --- examples/example.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/example.py b/examples/example.py index 3e28548..5997803 100644 --- a/examples/example.py +++ b/examples/example.py @@ -16,15 +16,15 @@ enable_IPv6 = , ) - # url - Required - String - The IP / Hostname of the Timbr platform. - # ontology - Required - String - The ontology / knowledge graph to connect to. - # token - Required - String - Timbr token value or JWT token value. Note: If you are using JWT token, you need to set the is_jwt parameter to True. - # query - Required - String - The query that you want to execute. - # datasource - Optional - String - Add the specific datasource name that you want to query from, the default value is the current active datasource of your ontology. - # nested - Optional - String - Change to 'true' if nested flag needs to be enabled. make sure this flag contains string value not bool value. - # verify_ssl - Optional - Boolean - Verifying the target server's SSL Certificate, use False to disable this process. - # enable_IPv6 - Optional - Boolean - Change to 'true' if you are using IPv6 connection. - # is_jwt - Optional - Boolean - Set to True if you are using JWT token, otherwise set to False. + # url - Required - String - The IP / Hostname of the Timbr platform. + # ontology - Required - String - The ontology / knowledge graph to connect to. + # token - Required - String - Timbr token value or JWT token value. Note: If you are using JWT token, you need to set the is_jwt parameter to True. + # query - Required - String - The query that you want to execute. + # datasource - Optional - String - Add the specific datasource name that you want to query from, the default value is the current active datasource of your ontology. + # nested - Optional - String - Change to 'true' if nested flag needs to be enabled. make sure this flag contains string value not bool value. + # verify_ssl - Optional - Boolean - Verifying the target server's SSL Certificate, use False to disable this process. + # enable_IPv6 - Optional - Boolean - Change to 'true' if you are using IPv6 connection. + # is_jwt - Optional - Boolean - Set to True if you are using JWT token, otherwise set to False. # jwt_tenant_id - Optional - String - The tenant ID for JWT authentication # HTTP example