Skip to content

Commit 5ba9df4

Browse files
Merge branch 'master' into docs/fix-typo-docstring
2 parents f538553 + 66429f1 commit 5ba9df4

File tree

13 files changed

+594
-214
lines changed

13 files changed

+594
-214
lines changed

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
uses: actions/checkout@v4
2323

2424
- name: Setup Pages
25-
uses: actions/configure-pages@v4
25+
uses: actions/configure-pages@v5
2626

2727
- name: Configure Python
2828
uses: actions/setup-python@v5

.github/workflows/publish.yml

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,17 @@ permissions:
1212

1313
jobs:
1414
rl-scanner:
15-
uses: ./.github/workflows/rl-scanner.yml
16-
with:
17-
node-version: 18
18-
artifact-name: "auth0-python.tgz"
19-
secrets:
20-
RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }}
21-
RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }}
22-
SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }}
23-
PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }}
24-
PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }}
25-
PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }}
15+
uses: ./.github/workflows/rl-scanner.yml
16+
with:
17+
python-version: 3.10
18+
artifact-name: "auth0-python.tgz"
19+
secrets:
20+
RLSECURE_LICENSE: ${{ secrets.RLSECURE_LICENSE }}
21+
RLSECURE_SITE_KEY: ${{ secrets.RLSECURE_SITE_KEY }}
22+
SIGNAL_HANDLER_TOKEN: ${{ secrets.SIGNAL_HANDLER_TOKEN }}
23+
PRODSEC_TOOLS_USER: ${{ secrets.PRODSEC_TOOLS_USER }}
24+
PRODSEC_TOOLS_TOKEN: ${{ secrets.PRODSEC_TOOLS_TOKEN }}
25+
PRODSEC_TOOLS_ARN: ${{ secrets.PRODSEC_TOOLS_ARN }}
2626
publish-pypi:
2727
if: github.event_name == 'workflow_dispatch' || (github.event_name == 'pull_request' && github.event.pull_request.merged && startsWith(github.event.pull_request.head.ref, 'release/'))
2828
name: "PyPI"

.github/workflows/snyk.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
with:
3535
ref: ${{ github.event.pull_request.head.sha || github.ref }}
3636

37-
- uses: snyk/actions/python-3.8@4a528b5c534bb771b6e3772656a8e0e9dc902f8b # pinned 2023-06-13
37+
- uses: snyk/actions/python-3.8@cdb760004ba9ea4d525f2e043745dfe85bb9077e # pinned 2023-06-13
3838
continue-on-error: true # Make sure the SARIF upload is called
3939
env:
4040
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}

.github/workflows/test.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,4 @@ jobs:
8080

8181
- if: ${{ matrix.python-version == '3.10' }}
8282
name: Upload coverage
83-
uses: codecov/codecov-action@4fe8c5f003fae66aa5ebb77cfd3e7bfbbda0b6b0 # [email protected].5
83+
uses: codecov/codecov-action@13ce06bfc6bbe3ecf90edbbf1bc32fe5978ca1d3 # pin@5.3.1
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import Any
2+
3+
from .base import AuthenticationBase
4+
5+
6+
class BackChannelLogin(AuthenticationBase):
7+
"""Back-Channel Login endpoint"""
8+
9+
def back_channel_login(
10+
self, binding_message: str, login_hint: str, scope: str, **kwargs
11+
) -> Any:
12+
"""Send a Back-Channel Login.
13+
14+
Args:
15+
binding_message (str): Human-readable string displayed on both the device calling /bc-authorize and the user’s
16+
authentication device to ensure the user is approves the correct request.
17+
18+
login_hint (str): String containing information about the user to contact for authentication.
19+
20+
scope(str): "openid" is a required scope.Multiple scopes are separated
21+
with whitespace.
22+
23+
**kwargs: Other fields to send along with the PAR.
24+
25+
Returns:
26+
auth_req_id, expires_in, interval
27+
"""
28+
return self.authenticated_post(
29+
f"{self.protocol}://{self.domain}/bc-authorize",
30+
data={
31+
"client_id": self.client_id,
32+
"binding_message": binding_message,
33+
"login_hint": login_hint,
34+
"scope": scope,
35+
**kwargs,
36+
},
37+
)

auth0/authentication/get_token.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,3 +253,27 @@ def passwordless_login(
253253
"grant_type": "http://auth0.com/oauth/grant-type/passwordless/otp",
254254
},
255255
)
256+
257+
def backchannel_login(
258+
self, auth_req_id: str, grant_type: str = "urn:openid:params:grant-type:ciba",
259+
) -> Any:
260+
"""Calls /oauth/token endpoint with "urn:openid:params:grant-type:ciba" grant type
261+
262+
Args:
263+
auth_req_id (str): The id received from /bc-authorize
264+
265+
grant_type (str): Denotes the flow you're using.For Back Channel login
266+
use urn:openid:params:grant-type:ciba
267+
268+
Returns:
269+
access_token, id_token
270+
"""
271+
272+
return self.authenticated_post(
273+
f"{self.protocol}://{self.domain}/oauth/token",
274+
data={
275+
"client_id": self.client_id,
276+
"auth_req_id": auth_req_id,
277+
"grant_type": grant_type,
278+
},
279+
)

auth0/authentication/pushed_authorization_requests.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ def pushed_authorization_request(
1616
redirect_uri (str): The URL to which Auth0 will redirect the browser after authorization has been granted
1717
by the user.
1818
**kwargs: Other fields to send along with the PAR.
19+
For RAR requests, authorization_details parameter should be added in a proper format. See:https://datatracker.ietf.org/doc/html/rfc9396
20+
For JAR requests, requests parameter should be send with the JWT as the value. See: https://datatracker.ietf.org/doc/html/rfc9126#name-the-request-request-paramet
1921
2022
See: https://www.rfc-editor.org/rfc/rfc9126.html
2123
"""
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
2+
import unittest
3+
from unittest import mock
4+
5+
import requests
6+
from ...exceptions import Auth0Error, RateLimitError
7+
8+
from ...authentication.back_channel_login import BackChannelLogin
9+
10+
class TestBackChannelLogin(unittest.TestCase):
11+
@mock.patch("auth0.rest.RestClient.post")
12+
def test_ciba(self, mock_post):
13+
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")
14+
15+
g.back_channel_login(
16+
binding_message="This is a binding message",
17+
login_hint="{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }",
18+
scope="openid",
19+
)
20+
21+
args, kwargs = mock_post.call_args
22+
23+
self.assertEqual(args[0], "https://my.domain.com/bc-authorize")
24+
self.assertEqual(
25+
kwargs["data"],
26+
{
27+
"client_id": "cid",
28+
"client_secret": "clsec",
29+
"binding_message": "This is a binding message",
30+
"login_hint": "{ \"format\": \"iss_sub\", \"iss\": \"https://my.domain.auth0.com/\", \"sub\": \"auth0|[USER ID]\" }",
31+
"scope": "openid",
32+
},
33+
)
34+
35+
@mock.patch("auth0.rest.RestClient.post")
36+
def test_should_require_binding_message(self, mock_post):
37+
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")
38+
39+
# Expecting an exception to be raised when binding_message is missing
40+
with self.assertRaises(Exception) as context:
41+
g.back_channel_login(
42+
login_hint='{ "format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID" }',
43+
scope="openid",
44+
)
45+
46+
# Assert the error message is correct
47+
self.assertIn("missing 1 required positional argument: \'binding_message\'", str(context.exception))
48+
49+
@mock.patch("auth0.rest.RestClient.post")
50+
def test_should_require_login_hint(self, mock_post):
51+
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")
52+
53+
# Expecting an exception to be raised when login_hint is missing
54+
with self.assertRaises(Exception) as context:
55+
g.back_channel_login(
56+
binding_message="This is a binding message.",
57+
scope="openid",
58+
)
59+
60+
# Assert the error message is correct
61+
self.assertIn("missing 1 required positional argument: \'login_hint\'", str(context.exception))
62+
63+
@mock.patch("auth0.rest.RestClient.post")
64+
def test_should_require_scope(self, mock_post):
65+
g = BackChannelLogin("my.domain.com", "cid", client_secret="clsec")
66+
67+
# Expecting an exception to be raised when scope is missing
68+
with self.assertRaises(Exception) as context:
69+
g.back_channel_login(
70+
binding_message="This is a binding message.",
71+
login_hint='{ "format": "iss_sub", "iss": "https://my.domain.auth0.com/", "sub": "auth0|USER_ID" }',
72+
)
73+
74+
# Assert the error message is correct
75+
self.assertIn("missing 1 required positional argument: \'scope\'", str(context.exception))
76+
77+
78+

auth0/test/authentication/test_get_token.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,25 @@ def test_passwordless_login_with_email(self, mock_post):
313313
"scope": "openid",
314314
},
315315
)
316+
317+
@mock.patch("auth0.rest.RestClient.post")
318+
def test_backchannel_login(self, mock_post):
319+
g = GetToken("my.domain.com", "cid", client_secret="csec")
320+
321+
g.backchannel_login(
322+
auth_req_id="reqid",
323+
grant_type="urn:openid:params:grant-type:ciba",
324+
)
325+
326+
args, kwargs = mock_post.call_args
327+
328+
self.assertEqual(args[0], "https://my.domain.com/oauth/token")
329+
self.assertEqual(
330+
kwargs["data"],
331+
{
332+
"client_id": "cid",
333+
"client_secret": "csec",
334+
"auth_req_id": "reqid",
335+
"grant_type": "urn:openid:params:grant-type:ciba",
336+
},
337+
)

auth0/test/authentication/test_pushed_authorization_requests.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import unittest
2+
import json
23
from unittest import mock
34

45
from ...authentication.pushed_authorization_requests import PushedAuthorizationRequests
@@ -45,3 +46,59 @@ def test_par_custom_params(self, mock_post):
4546
"foo": "bar",
4647
},
4748
)
49+
50+
@mock.patch("auth0.rest.RestClient.post")
51+
def test_rar(self, mock_post):
52+
a = PushedAuthorizationRequests("my.domain.com", "cid", client_secret="sh!")
53+
a.pushed_authorization_request(
54+
response_type="code",
55+
redirect_uri="https://example.com/callback",
56+
authorization_details=[{"type": "money_transfer", "instructedAmount": {"amount": 2500, "currency": "USD"}}],
57+
)
58+
59+
args, kwargs = mock_post.call_args
60+
61+
expected_data = {
62+
"client_id": "cid",
63+
"client_secret": "sh!",
64+
"response_type": "code",
65+
"redirect_uri": "https://example.com/callback",
66+
"authorization_details": [{"type": "money_transfer", "instructedAmount": {"amount": 2500, "currency": "USD"}}],
67+
}
68+
69+
actual_data = kwargs["data"]
70+
71+
self.assertEqual(args[0], "https://my.domain.com/oauth/par")
72+
73+
self.assertEqual(
74+
json.dumps(actual_data, sort_keys=True),
75+
json.dumps(expected_data, sort_keys=True)
76+
)
77+
78+
@mock.patch("auth0.rest.RestClient.post")
79+
def test_jar(self, mock_post):
80+
a = PushedAuthorizationRequests("my.domain.com", "cid", client_secret="sh!")
81+
a.pushed_authorization_request(
82+
response_type="code",
83+
redirect_uri="https://example.com/callback",
84+
request="my-jwt-request",
85+
)
86+
87+
args, kwargs = mock_post.call_args
88+
89+
expected_data = {
90+
"client_id": "cid",
91+
"client_secret": "sh!",
92+
"response_type": "code",
93+
"redirect_uri": "https://example.com/callback",
94+
"request": 'my-jwt-request',
95+
}
96+
97+
actual_data = kwargs["data"]
98+
99+
self.assertEqual(args[0], "https://my.domain.com/oauth/par")
100+
101+
self.assertEqual(
102+
json.dumps(actual_data, sort_keys=True),
103+
json.dumps(expected_data, sort_keys=True)
104+
)

0 commit comments

Comments
 (0)