Skip to content

Commit d269f27

Browse files
committed
Merge branch '232-ensure-authorization-requests-have-a-valid-redirect-uri' into 'main'
Resolve "Ensure authorization requests have a valid redirect URI" Closes #232 See merge request yaal/canaille!241
2 parents 4be7b16 + 467addc commit d269f27

11 files changed

+66
-3
lines changed

CHANGES.rst

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Added
99
Fixed
1010
^^^^^
1111
- Prevent clients from registering with fragment components in their redirect uri :issue:`235`
12+
- Ensure there is a `redirect_uri` in authorization requests from clients. :issue:`232`
1213

1314
[0.0.64] - 2025-02-12
1415
---------------------

canaille/oidc/endpoints/oauth.py

+7
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from authlib.jose import jwt
77
from authlib.jose.errors import JoseError
88
from authlib.oauth2 import OAuth2Error
9+
from authlib.oauth2.rfc6749.errors import InvalidRequestError
910
from flask import Blueprint
1011
from flask import abort
1112
from flask import current_app
@@ -124,6 +125,12 @@ def authorize_login(user):
124125

125126

126127
def authorize_consent(client, user):
128+
redirect_uri = request.args.get("redirect_uri")
129+
# Ensures the request contains a redirect_uri until resolved upstream in Authlib
130+
# https://github.com/lepture/authlib/issues/712
131+
if not redirect_uri:
132+
raise InvalidRequestError('Missing "redirect_uri" in request.')
133+
127134
requested_scopes = request.args.get("scope", "").split(" ")
128135
allowed_scopes = client.get_allowed_scope(requested_scopes).split(" ")
129136
consents = Backend.instance.query(

canaille/oidc/models.py

-3
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,6 @@ def __init__(self, *args, **kwargs):
4949
def get_client_id(self):
5050
return self.client_id
5151

52-
def get_default_redirect_uri(self):
53-
return self.redirect_uris[0]
54-
5552
def get_allowed_scope(self, scope):
5653
return util.list_to_scope(
5754
[scope_piece for scope_piece in self.scope if scope_piece in scope]

tests/oidc/test_authorization_code_flow.py

+40
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
from urllib.parse import parse_qs
44
from urllib.parse import urlsplit
55

6+
import pytest
67
import time_machine
78
from authlib.jose import jwt
9+
from authlib.oauth2.rfc6749.errors import InvalidRequestError
810
from authlib.oauth2.rfc7636 import create_s256_code_challenge
911
from flask import g
1012
from werkzeug.security import gen_salt
@@ -27,6 +29,7 @@ def test_nominal_case(
2729
client_id=client.client_id,
2830
scope="openid profile email groups address phone",
2931
nonce="somenonce",
32+
redirect_uri="https://client.test/redirect1",
3033
),
3134
status=200,
3235
)
@@ -119,6 +122,7 @@ def test_invalid_client(testclient, logged_user, keypair):
119122
client_id="invalid",
120123
scope="openid profile email groups address phone",
121124
nonce="somenonce",
125+
redirect_uri="https://client.test/redirect1",
122126
),
123127
status=400,
124128
)
@@ -186,6 +190,7 @@ def test_preconsented_client(
186190
client_id=client.client_id,
187191
scope="openid profile",
188192
nonce="somenonce",
193+
redirect_uri="https://client.test/redirect1",
189194
),
190195
status=302,
191196
)
@@ -240,6 +245,7 @@ def test_logout_login(testclient, logged_user, client, backend):
240245
client_id=client.client_id,
241246
scope="openid profile",
242247
nonce="somenonce",
248+
redirect_uri="https://client.test/redirect1",
243249
),
244250
status=200,
245251
)
@@ -311,6 +317,7 @@ def test_deny(testclient, logged_user, client, backend):
311317
client_id=client.client_id,
312318
scope="openid profile",
313319
nonce="somenonce",
320+
redirect_uri="https://client.test/redirect1",
314321
),
315322
status=200,
316323
)
@@ -342,6 +349,7 @@ def test_code_challenge(testclient, logged_user, client, backend):
342349
client_id=client.client_id,
343350
scope="openid profile",
344351
nonce="somenonce",
352+
redirect_uri="https://client.test/redirect1",
345353
),
346354
status=200,
347355
)
@@ -399,6 +407,7 @@ def test_consent_already_given(testclient, logged_user, client, backend):
399407
client_id=client.client_id,
400408
scope="openid profile",
401409
nonce="somenonce",
410+
redirect_uri="https://client.test/redirect1",
402411
),
403412
status=200,
404413
)
@@ -434,6 +443,7 @@ def test_consent_already_given(testclient, logged_user, client, backend):
434443
client_id=client.client_id,
435444
scope="openid profile",
436445
nonce="somenonce",
446+
redirect_uri="https://client.test/redirect1",
437447
),
438448
status=302,
439449
)
@@ -455,6 +465,7 @@ def test_consent_with_openid_scope_only(testclient, logged_user, client, backend
455465
client_id=client.client_id,
456466
scope="openid",
457467
nonce="somenonce",
468+
redirect_uri="https://client.test/redirect1",
458469
),
459470
status=200,
460471
)
@@ -474,6 +485,7 @@ def test_consent_with_no_scope(testclient, logged_user, client, backend):
474485
response_type="code",
475486
client_id=client.client_id,
476487
nonce="somenonce",
488+
redirect_uri="https://client.test/redirect1",
477489
),
478490
status=200,
479491
)
@@ -483,6 +495,22 @@ def test_consent_with_no_scope(testclient, logged_user, client, backend):
483495
res.mustcontain(no="Accept")
484496

485497

498+
def test_consent_with_no_redirect_uri(testclient, logged_user, client, backend):
499+
with pytest.raises(
500+
InvalidRequestError,
501+
match=r'Missing "redirect_uri" in request.',
502+
):
503+
testclient.get(
504+
"/oauth/authorize",
505+
params=dict(
506+
response_type="code",
507+
client_id=client.client_id,
508+
nonce="somenonce",
509+
),
510+
status=200,
511+
)
512+
513+
486514
def test_when_consent_already_given_but_for_a_smaller_scope(
487515
testclient, logged_user, client, backend
488516
):
@@ -495,6 +523,7 @@ def test_when_consent_already_given_but_for_a_smaller_scope(
495523
client_id=client.client_id,
496524
scope="openid profile",
497525
nonce="somenonce",
526+
redirect_uri="https://client.test/redirect1",
498527
),
499528
status=200,
500529
)
@@ -531,6 +560,7 @@ def test_when_consent_already_given_but_for_a_smaller_scope(
531560
client_id=client.client_id,
532561
scope="openid profile groups",
533562
nonce="somenonce",
563+
redirect_uri="https://client.test/redirect1",
534564
),
535565
status=200,
536566
)
@@ -564,6 +594,7 @@ def test_user_cannot_use_oidc(
564594
client_id=client.client_id,
565595
scope="openid profile",
566596
nonce="somenonce",
597+
redirect_uri="https://client.test/redirect1",
567598
),
568599
)
569600
res = res.follow()
@@ -584,6 +615,7 @@ def test_nonce_required_in_oidc_requests(testclient, logged_user, client):
584615
response_type="code",
585616
client_id=client.client_id,
586617
scope="openid profile",
618+
redirect_uri="https://client.test/redirect1",
587619
),
588620
status=200,
589621
)
@@ -601,6 +633,7 @@ def test_nonce_not_required_in_oauth_requests(testclient, logged_user, client, b
601633
response_type="code",
602634
client_id=client.client_id,
603635
scope="profile",
636+
redirect_uri="https://client.test/redirect1",
604637
),
605638
status=200,
606639
)
@@ -624,6 +657,7 @@ def test_request_scope_too_large(testclient, logged_user, keypair, client, backe
624657
client_id=client.client_id,
625658
scope="openid profile email",
626659
nonce="somenonce",
660+
redirect_uri="https://client.test/redirect1",
627661
),
628662
status=200,
629663
)
@@ -690,6 +724,7 @@ def test_code_expired(testclient, user, client):
690724
client_id=client.client_id,
691725
scope="openid profile email groups address phone",
692726
nonce="somenonce",
727+
redirect_uri="https://client.test/redirect1",
693728
),
694729
)
695730
res = res.follow()
@@ -738,6 +773,7 @@ def test_code_with_invalid_user(testclient, admin, client, backend):
738773
client_id=client.client_id,
739774
scope="openid profile email groups address phone",
740775
nonce="somenonce",
776+
redirect_uri="https://client.test/redirect1",
741777
),
742778
).follow()
743779

@@ -782,6 +818,7 @@ def test_locked_account(
782818
client_id=client.client_id,
783819
scope="openid profile email groups address phone",
784820
nonce="somenonce",
821+
redirect_uri="https://client.test/redirect1",
785822
),
786823
status=200,
787824
)
@@ -822,6 +859,7 @@ def test_missing_client_id(
822859
response_type="code",
823860
scope="openid profile email groups address phone",
824861
nonce="somenonce",
862+
redirect_uri="https://client.test/redirect1",
825863
),
826864
status=400,
827865
)
@@ -844,6 +882,7 @@ def test_logout_login_with_intruder_lockout(testclient, logged_user, client, bac
844882
client_id=client.client_id,
845883
scope="openid profile",
846884
nonce="somenonce",
885+
redirect_uri="https://client.test/redirect1",
847886
),
848887
status=200,
849888
)
@@ -892,6 +931,7 @@ def test_rfc9207(
892931
client_id=client.client_id,
893932
scope="openid profile email groups address phone",
894933
nonce="somenonce",
934+
redirect_uri="https://client.test/redirect1",
895935
),
896936
status=200,
897937
)

tests/oidc/test_authorization_prompt.py

+6
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def test_prompt_none(testclient, logged_user, client, backend):
3232
scope="openid profile",
3333
nonce="somenonce",
3434
prompt="none",
35+
redirect_uri="https://client.test/redirect1",
3536
),
3637
status=302,
3738
)
@@ -67,6 +68,7 @@ def test_prompt_not_logged(testclient, user, client, backend):
6768
scope="openid profile",
6869
nonce="somenonce",
6970
prompt="none",
71+
redirect_uri="https://client.test/redirect1",
7072
),
7173
status=200,
7274
)
@@ -92,6 +94,7 @@ def test_prompt_no_consent(testclient, logged_user, client):
9294
scope="openid profile",
9395
nonce="somenonce",
9496
prompt="none",
97+
redirect_uri="https://client.test/redirect1",
9598
),
9699
status=200,
97100
)
@@ -118,6 +121,7 @@ def test_prompt_create_logged(testclient, logged_user, client, backend):
118121
scope="openid profile",
119122
nonce="somenonce",
120123
prompt="create",
124+
redirect_uri="https://client.test/redirect1",
121125
),
122126
status=302,
123127
)
@@ -144,6 +148,7 @@ def test_prompt_create_registration_disabled(testclient, trusted_client, smtpd):
144148
scope="openid profile",
145149
nonce="somenonce",
146150
prompt="create",
151+
redirect_uri="https://client.test/redirect1",
147152
),
148153
status=400,
149154
)
@@ -169,6 +174,7 @@ def test_prompt_create_not_logged(testclient, trusted_client, smtpd):
169174
scope="openid profile",
170175
nonce="somenonce",
171176
prompt="create",
177+
redirect_uri=trusted_client.redirect_uris[0],
172178
),
173179
)
174180

tests/oidc/test_consent.py

+1
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ def test_oidc_authorization_after_revokation(
114114
client_id=client.client_id,
115115
scope="openid profile",
116116
nonce="somenonce",
117+
redirect_uri="https://client.test/redirect1",
117118
),
118119
status=200,
119120
)

tests/oidc/test_hybrid_flow.py

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ def test_oauth_hybrid(testclient, backend, user, client):
1414
client_id=client.client_id,
1515
scope="openid profile",
1616
nonce="somenonce",
17+
redirect_uri="https://client.test/redirect1",
1718
),
1819
).follow()
1920
assert "text/html" == res.content_type, res.json
@@ -55,6 +56,7 @@ def test_oidc_hybrid(testclient, backend, logged_user, client, keypair, trusted_
5556
client_id=client.client_id,
5657
scope="openid profile",
5758
nonce="somenonce",
59+
redirect_uri="https://client.test/redirect1",
5860
),
5961
)
6062
assert "text/html" == res.content_type, res.json

tests/oidc/test_implicit_flow.py

+3
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def test_oauth_implicit(testclient, user, client, backend):
1919
client_id=client.client_id,
2020
scope="profile",
2121
nonce="somenonce",
22+
redirect_uri="https://client.test/redirect1",
2223
),
2324
).follow()
2425
assert "text/html" == res.content_type
@@ -64,6 +65,7 @@ def test_oidc_implicit(testclient, keypair, user, client, trusted_client, backen
6465
client_id=client.client_id,
6566
scope="openid profile",
6667
nonce="somenonce",
68+
redirect_uri="https://client.test/redirect1",
6769
),
6870
).follow()
6971
assert "text/html" == res.content_type
@@ -119,6 +121,7 @@ def test_oidc_implicit_with_group(
119121
client_id=client.client_id,
120122
scope="openid profile groups",
121123
nonce="somenonce",
124+
redirect_uri="https://client.test/redirect1",
122125
),
123126
).follow()
124127
assert "text/html" == res.content_type

tests/oidc/test_refresh_token.py

+3
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ def test_refresh_token(testclient, logged_user, client, backend, caplog):
1818
client_id=client.client_id,
1919
scope="openid profile",
2020
nonce="somenonce",
21+
redirect_uri="https://client.test/redirect1",
2122
),
2223
)
2324
res = res.form.submit(name="answer", value="accept")
@@ -96,6 +97,7 @@ def test_refresh_token_with_invalid_user(testclient, client, backend):
9697
client_id=client.client_id,
9798
scope="openid profile",
9899
nonce="somenonce",
100+
redirect_uri="https://client.test/redirect1",
99101
),
100102
).follow()
101103

@@ -154,6 +156,7 @@ def test_cannot_refresh_token_for_locked_users(
154156
client_id=client.client_id,
155157
scope="openid profile",
156158
nonce="somenonce",
159+
redirect_uri="https://client.test/redirect1",
157160
),
158161
)
159162
res = res.form.submit(name="answer", value="accept")

tests/oidc/test_token_expiration.py

+2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ def test_token_default_expiration_date(
1919
client_id=client.client_id,
2020
scope="openid profile email groups address phone",
2121
nonce="somenonce",
22+
redirect_uri="https://client.test/redirect1",
2223
),
2324
status=200,
2425
)
@@ -79,6 +80,7 @@ def test_token_custom_expiration_date(
7980
client_id=client.client_id,
8081
scope="openid profile email groups address phone",
8182
nonce="somenonce",
83+
redirect_uri="https://client.test/redirect1",
8284
),
8385
status=200,
8486
)

tests/oidc/test_token_introspection.py

+1
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ def test_full_flow(testclient, logged_user, client, user, trusted_client, backen
8585
client_id=client.client_id,
8686
scope="profile",
8787
nonce="somenonce",
88+
redirect_uri="https://client.test/redirect1",
8889
),
8990
status=200,
9091
)

0 commit comments

Comments
 (0)