From b95cb5a0eba0c19ea17c846ee7a69d4fc1864f8f Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 20:26:19 +0530 Subject: [PATCH 01/14] fix: support `authorization_details` --- auth0/authentication/back_channel_login.py | 37 +++++++++++++++---- .../authentication/test_back_channel_login.py | 18 ++++----- 2 files changed, 38 insertions(+), 17 deletions(-) diff --git a/auth0/authentication/back_channel_login.py b/auth0/authentication/back_channel_login.py index 1dc7d69f..fbc52e62 100644 --- a/auth0/authentication/back_channel_login.py +++ b/auth0/authentication/back_channel_login.py @@ -1,13 +1,20 @@ -from typing import Any +from typing import Any, Optional, Union from .base import AuthenticationBase +import json + class BackChannelLogin(AuthenticationBase): """Back-Channel Login endpoint""" def back_channel_login( - self, binding_message: str, login_hint: str, scope: str, **kwargs + self, + binding_message: str, + login_hint: str, + scope: str, + authorization_details: Optional[Union[str, list[dict]]] = None, + **kwargs ) -> Any: """Send a Back-Channel Login. @@ -15,24 +22,38 @@ def back_channel_login( binding_message (str): Human-readable string displayed on both the device calling /bc-authorize and the user’s authentication device to ensure the user is approves the correct request. - login_hint (str): String containing information about the user to contact for authentication. + login_hint (str): A JSON object containing user details for authentication in the iss_sub format. scope(str): "openid" is a required scope.Multiple scopes are separated with whitespace. - **kwargs: Other fields to send along with the PAR. + authorization_details (str, list of dict, or dict, optional): JSON string or dictionary representing + Rich Authorization Requests (RAR) details to include in the CIBA request. + + **kwargs: Other fields to send along with the request. Returns: auth_req_id, expires_in, interval """ - return self.authenticated_post( - f"{self.protocol}://{self.domain}/bc-authorize", - data={ + + data = { "client_id": self.client_id, "binding_message": binding_message, "login_hint": login_hint, "scope": scope, **kwargs, - }, + } + + if authorization_details is not None: + if isinstance(authorization_details, str): + data["authorization_details"] = authorization_details + elif isinstance(authorization_details, (list, dict)): + data["authorization_details"] = json.dumps(authorization_details) + + data.update(kwargs) + + return self.authenticated_post( + f"{self.protocol}://{self.domain}/bc-authorize", + data = data, headers={"Content-Type": "application/x-www-form-urlencoded"}, ) diff --git a/auth0/test/authentication/test_back_channel_login.py b/auth0/test/authentication/test_back_channel_login.py index 18206b17..db5f7edb 100644 --- a/auth0/test/authentication/test_back_channel_login.py +++ b/auth0/test/authentication/test_back_channel_login.py @@ -106,9 +106,9 @@ def test_with_authorization_details(self, mock_post): "client_id": "cid", "client_secret": "clsec", "binding_message": "This is a binding message.", - "login_hint": {"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID" }, + "login_hint": {"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID"}, "scope": "openid", - "authorization_details": [ + "authorization_details": json.dumps([ { "type":"payment_initiation","locations":["https://example.com/payments"], "instructedAmount": @@ -122,17 +122,17 @@ def test_with_authorization_details(self, mock_post): "iban":"DE021001001093071118603" }, "remittanceInformationUnstructured":"Ref Number Merchant" - }], + } + ]), } actual_data = kwargs["data"] - + self.assertEqual(args[0], "https://my.domain.com/bc-authorize") - + self.assertEqual( - json.dumps(actual_data, sort_keys=True), - json.dumps(expected_data, sort_keys=True) + actual_data, + expected_data, + "Request data does not match expected data after JSON serialization." ) - - From 5fa2719b6ebb78dfe1b5c4fae1f976b0a7e2f0c9 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 20:50:22 +0530 Subject: [PATCH 02/14] Making it v3.8 compatible --- auth0/authentication/back_channel_login.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/auth0/authentication/back_channel_login.py b/auth0/authentication/back_channel_login.py index fbc52e62..30af4472 100644 --- a/auth0/authentication/back_channel_login.py +++ b/auth0/authentication/back_channel_login.py @@ -1,4 +1,4 @@ -from typing import Any, Optional, Union +from typing import Any, Optional, Union, List, Dict from .base import AuthenticationBase @@ -13,7 +13,7 @@ def back_channel_login( binding_message: str, login_hint: str, scope: str, - authorization_details: Optional[Union[str, list[dict]]] = None, + authorization_details: Optional[Union[str, List[Dict]]] = None, **kwargs ) -> Any: """Send a Back-Channel Login. From b0703dcb376f957fafd998ba4d6e4fbf4f348850 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 20:59:36 +0530 Subject: [PATCH 03/14] Changing poetry version --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 020bad5f..15ec8861 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,7 +58,11 @@ jobs: pip install --user pipx pip install --user setuptools pipx ensurepath - pipx install poetry + if [[ "${{ matrix.python-version }}" == "3.8" ]]; then + pipx install poetry==1.5.1 + else + pipx install poetry + fi poetry config virtualenvs.in-project true poetry install --with dev poetry self add "poetry-dynamic-versioning[plugin]" From 4409d5d12fed73c933d2af5b908c6a5e558065e0 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 21:06:10 +0530 Subject: [PATCH 04/14] printing error logs --- .github/workflows/test.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 15ec8861..71194c2a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,11 @@ jobs: pip install --user setuptools pipx ensurepath if [[ "${{ matrix.python-version }}" == "3.8" ]]; then - pipx install poetry==1.5.1 + pipx install 'poetry==1.5.1' --pip-args='installer<0.7.0' || { + echo "❌ pipx install failed — printing pip log:" + cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" + exit 1 + } else pipx install poetry fi From 7bc1076d2df81913711dc5117b397d10bb92805b Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 21:10:13 +0530 Subject: [PATCH 05/14] Reverting to original workflow --- .github/workflows/test.yml | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 71194c2a..40f34181 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,15 +58,8 @@ jobs: pip install --user pipx pip install --user setuptools pipx ensurepath - if [[ "${{ matrix.python-version }}" == "3.8" ]]; then - pipx install 'poetry==1.5.1' --pip-args='installer<0.7.0' || { - echo "❌ pipx install failed — printing pip log:" - cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" - exit 1 - } - else - pipx install poetry - fi + pipx install poetry + cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" poetry config virtualenvs.in-project true poetry install --with dev poetry self add "poetry-dynamic-versioning[plugin]" From b15e2802c1167486b67d99e26d3bd4b2b02a5b6f Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 21:13:50 +0530 Subject: [PATCH 06/14] Checking Logs --- .github/workflows/test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 40f34181..6a899daf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -58,8 +58,11 @@ jobs: pip install --user pipx pip install --user setuptools pipx ensurepath - pipx install poetry + pipx install poetry || { + echo "❌ pipx install failed — printing pip log:" cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" + exit 1 + } poetry config virtualenvs.in-project true poetry install --with dev poetry self add "poetry-dynamic-versioning[plugin]" From a4eb152030bc54ed58b34184a86f00e5d7bc50d1 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 21:17:53 +0530 Subject: [PATCH 07/14] Adding fix for userpath --- .github/workflows/test.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6a899daf..61f23373 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -54,10 +54,11 @@ jobs: - name: Configure dependencies run: | sudo apt install bubblewrap - pip install --user --upgrade pip - pip install --user pipx + python -m pip install --upgrade pip setuptools + python -m pip install --user pipx pip install --user setuptools - pipx ensurepath + export PATH="$HOME/.local/bin:$PATH" + python -m pipx ensurepath pipx install poetry || { echo "❌ pipx install failed — printing pip log:" cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" From 3e4dc861c07161df6d2fac2d6a7c0978bc64a4e8 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 21:20:30 +0530 Subject: [PATCH 08/14] Adding poetry install fix --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 61f23373..ae3872a0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -59,7 +59,7 @@ jobs: pip install --user setuptools export PATH="$HOME/.local/bin:$PATH" python -m pipx ensurepath - pipx install poetry || { + python -m pipx install poetry || { echo "❌ pipx install failed — printing pip log:" cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" exit 1 From ccbe91c3e9616ff93bcc8dc8af67b98eba137098 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 21:26:43 +0530 Subject: [PATCH 09/14] Adding curl_path --- .github/workflows/test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae3872a0..017b9f48 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,6 +57,7 @@ jobs: python -m pip install --upgrade pip setuptools python -m pip install --user pipx pip install --user setuptools + curl -sSf https://raw.githubusercontent.com/pypa/pipx/main/install.sh | bash export PATH="$HOME/.local/bin:$PATH" python -m pipx ensurepath python -m pipx install poetry || { From 01dd04be84a70686667ee3e4a38fcb5ad6dc02bb Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 22:00:37 +0530 Subject: [PATCH 10/14] Adding the path --- .github/workflows/test.yml | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 017b9f48..e6b71b8a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -57,13 +57,16 @@ jobs: python -m pip install --upgrade pip setuptools python -m pip install --user pipx pip install --user setuptools - curl -sSf https://raw.githubusercontent.com/pypa/pipx/main/install.sh | bash + python -m pipx ensurepath export PATH="$HOME/.local/bin:$PATH" - python -m pipx ensurepath - python -m pipx install poetry || { - echo "❌ pipx install failed — printing pip log:" - cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" - exit 1 + if [[ "${{ matrix.python-version }}" == "3.8" ]]; then + python -m pipx install 'poetry==1.5.1' --pip-args='installer<0.7.0' + else + python -m pipx install poetry + fi || { + echo "❌ pipx install failed — printing pip log:" + cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" + exit 1 } poetry config virtualenvs.in-project true poetry install --with dev From 4582ecdaa1f696d5ac7b3c79a90a5dddbbb9eeb0 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Mon, 2 Jun 2025 22:09:28 +0530 Subject: [PATCH 11/14] Removing v3.8 from tests --- .github/workflows/test.yml | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e6b71b8a..8db7e526 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -38,7 +38,7 @@ jobs: strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - name: Checkout code @@ -54,20 +54,11 @@ jobs: - name: Configure dependencies run: | sudo apt install bubblewrap - python -m pip install --upgrade pip setuptools - python -m pip install --user pipx + pip install --user --upgrade pip + pip install --user pipx pip install --user setuptools - python -m pipx ensurepath - export PATH="$HOME/.local/bin:$PATH" - if [[ "${{ matrix.python-version }}" == "3.8" ]]; then - python -m pipx install 'poetry==1.5.1' --pip-args='installer<0.7.0' - else - python -m pipx install poetry - fi || { - echo "❌ pipx install failed — printing pip log:" - cat /opt/pipx/logs/*_pip_errors.log || echo "Log not found" - exit 1 - } + pipx ensurepath + pipx install poetry poetry config virtualenvs.in-project true poetry install --with dev poetry self add "poetry-dynamic-versioning[plugin]" @@ -91,4 +82,4 @@ jobs: name: Upload coverage uses: codecov/codecov-action@ad3126e916f78f00edff4ed0317cf185271ccc2d # pin@5.4.2 with: - token: ${{ secrets.CODECOV_TOKEN }} + token: ${{ secrets.CODECOV_TOKEN }} \ No newline at end of file From 65a96b0a5d4eea01d2a445e3dd0e062a6d3442b5 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Tue, 3 Jun 2025 19:18:45 +0530 Subject: [PATCH 12/14] Serialization Docstring Update --- auth0/authentication/back_channel_login.py | 6 ++++-- auth0/test/authentication/test_back_channel_login.py | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/auth0/authentication/back_channel_login.py b/auth0/authentication/back_channel_login.py index 30af4472..033407e8 100644 --- a/auth0/authentication/back_channel_login.py +++ b/auth0/authentication/back_channel_login.py @@ -22,13 +22,15 @@ def back_channel_login( binding_message (str): Human-readable string displayed on both the device calling /bc-authorize and the user’s authentication device to ensure the user is approves the correct request. - login_hint (str): A JSON object containing user details for authentication in the iss_sub format. + login_hint (str): JSON string containing user details for authentication in the iss_sub format.Ensure + serialization before passing. scope(str): "openid" is a required scope.Multiple scopes are separated with whitespace. - authorization_details (str, list of dict, or dict, optional): JSON string or dictionary representing + authorization_details (str, list of dict, optional): JSON string or dictionary representing Rich Authorization Requests (RAR) details to include in the CIBA request. + If a Python list is provided, the SDK automatically serializes it to a JSON string before sending. **kwargs: Other fields to send along with the request. diff --git a/auth0/test/authentication/test_back_channel_login.py b/auth0/test/authentication/test_back_channel_login.py index db5f7edb..049477ac 100644 --- a/auth0/test/authentication/test_back_channel_login.py +++ b/auth0/test/authentication/test_back_channel_login.py @@ -80,7 +80,7 @@ def test_with_authorization_details(self, mock_post): g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec") g.back_channel_login( binding_message="This is a binding message.", - login_hint={"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID"}, + login_hint= json.dumps({"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID"}), scope="openid", authorization_details=[ { @@ -106,7 +106,7 @@ def test_with_authorization_details(self, mock_post): "client_id": "cid", "client_secret": "clsec", "binding_message": "This is a binding message.", - "login_hint": {"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID"}, + "login_hint": json.dumps({"format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID"}), "scope": "openid", "authorization_details": json.dumps([ { From 0be92a43c2c297f4a45b5071f0aa2813da2302c4 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Tue, 3 Jun 2025 19:21:06 +0530 Subject: [PATCH 13/14] Removed dicts from the serialization --- auth0/authentication/back_channel_login.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/auth0/authentication/back_channel_login.py b/auth0/authentication/back_channel_login.py index 033407e8..98799ea3 100644 --- a/auth0/authentication/back_channel_login.py +++ b/auth0/authentication/back_channel_login.py @@ -49,7 +49,7 @@ def back_channel_login( if authorization_details is not None: if isinstance(authorization_details, str): data["authorization_details"] = authorization_details - elif isinstance(authorization_details, (list, dict)): + elif isinstance(authorization_details, (list)): data["authorization_details"] = json.dumps(authorization_details) data.update(kwargs) From fa71d66364c5b66ca2f068fc7afba75d75bb4c93 Mon Sep 17 00:00:00 2001 From: Snehil Kishore Date: Wed, 4 Jun 2025 10:40:58 +0530 Subject: [PATCH 14/14] Review Feedback Changes --- auth0/authentication/back_channel_login.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/auth0/authentication/back_channel_login.py b/auth0/authentication/back_channel_login.py index 98799ea3..e275cde4 100644 --- a/auth0/authentication/back_channel_login.py +++ b/auth0/authentication/back_channel_login.py @@ -28,9 +28,8 @@ def back_channel_login( scope(str): "openid" is a required scope.Multiple scopes are separated with whitespace. - authorization_details (str, list of dict, optional): JSON string or dictionary representing + authorization_details (str, list of dict, optional): JSON string or a list of dictionaries representing Rich Authorization Requests (RAR) details to include in the CIBA request. - If a Python list is provided, the SDK automatically serializes it to a JSON string before sending. **kwargs: Other fields to send along with the request. @@ -49,7 +48,7 @@ def back_channel_login( if authorization_details is not None: if isinstance(authorization_details, str): data["authorization_details"] = authorization_details - elif isinstance(authorization_details, (list)): + elif isinstance(authorization_details, list): data["authorization_details"] = json.dumps(authorization_details) data.update(kwargs)