1
1
from unittest .mock import MagicMock
2
2
3
3
import pytest
4
+ from authlib .integrations .starlette_client .apps import StarletteOAuth2App
4
5
from fastapi import HTTPException
5
6
from handler .auth .base_handler import OpenIDHandler
6
7
from joserfc .jwt import Token
@@ -50,6 +51,67 @@ def mock_token():
50
51
}
51
52
52
53
54
+ @pytest .fixture
55
+ def mock_openid_configuration ():
56
+ return {
57
+ "issuer" : "https://authentik.example.com/application/o/romm/" ,
58
+ "authorization_endpoint" : "https://authentik.example.com/application/o/authorize/" ,
59
+ "token_endpoint" : "https://authentik.example.com/application/o/token/" ,
60
+ "userinfo_endpoint" : "https://authentik.example.com/application/o/userinfo/" ,
61
+ "end_session_endpoint" : "https://authentik.example.com/application/o/romm/end-session/" ,
62
+ "introspection_endpoint" : "https://authentik.example.com/application/o/introspect/" ,
63
+ "revocation_endpoint" : "https://authentik.example.com/application/o/revoke/" ,
64
+ "device_authorization_endpoint" : "https://authentik.example.com/application/o/device/" ,
65
+ "response_types_supported" : [
66
+ "code" ,
67
+ "id_token" ,
68
+ "id_token token" ,
69
+ "code token" ,
70
+ "code id_token" ,
71
+ "code id_token token" ,
72
+ ],
73
+ "response_modes_supported" : ["query" , "fragment" , "form_post" ],
74
+ "jwks_uri" : "https://authentik.example.com/application/o/romm/jwks/" ,
75
+ "grant_types_supported" : [
76
+ "authorization_code" ,
77
+ "refresh_token" ,
78
+ "implicit" ,
79
+ "client_credentials" ,
80
+ "password" ,
81
+ "urn:ietf:params:oauth:grant-type:device_code" ,
82
+ ],
83
+ "id_token_signing_alg_values_supported" : ["RS256" ],
84
+ "subject_types_supported" : ["public" ],
85
+ "token_endpoint_auth_methods_supported" : [
86
+ "client_secret_post" ,
87
+ "client_secret_basic" ,
88
+ ],
89
+ "acr_values_supported" : ["goauthentik.io/providers/oauth2/default" ],
90
+ "scopes_supported" : ["openid" , "email" , "profile" ],
91
+ "request_parameter_supported" : False ,
92
+ "claims_supported" : [
93
+ "sub" ,
94
+ "iss" ,
95
+ "aud" ,
96
+ "exp" ,
97
+ "iat" ,
98
+ "auth_time" ,
99
+ "acr" ,
100
+ "amr" ,
101
+ "nonce" ,
102
+ "email" ,
103
+ "email_verified" ,
104
+ "name" ,
105
+ "given_name" ,
106
+ "preferred_username" ,
107
+ "nickname" ,
108
+ "groups" ,
109
+ ],
110
+ "claims_parameter_supported" : False ,
111
+ "code_challenge_methods_supported" : ["plain" , "S256" ],
112
+ }
113
+
114
+
53
115
async def test_oidc_disabled (mock_oidc_disabled , mock_token ):
54
116
"""Test that OIDC is disabled."""
55
117
oidc_handler = OpenIDHandler ()
@@ -60,7 +122,9 @@ async def test_oidc_disabled(mock_oidc_disabled, mock_token):
60
122
assert userinfo is None
61
123
62
124
63
- async def test_oidc_valid_token_decoding (mocker , mock_oidc_enabled , mock_token ):
125
+ async def test_oidc_valid_token_decoding (
126
+ mocker , mock_oidc_enabled , mock_token , mock_openid_configuration
127
+ ):
64
128
"""Test token decoding with valid RSA key and token."""
65
129
mock_jwt_payload = Token (
66
130
header = {"alg" : "RS256" },
@@ -70,6 +134,65 @@ async def test_oidc_valid_token_decoding(mocker, mock_oidc_enabled, mock_token):
70
134
mocker .patch (
71
135
"handler.database.db_user_handler.get_user_by_email" , return_value = mock_user
72
136
)
137
+ mocker .patch .object (
138
+ StarletteOAuth2App ,
139
+ "load_server_metadata" ,
140
+ return_value = mock_openid_configuration ,
141
+ )
142
+
143
+ oidc_handler = OpenIDHandler ()
144
+ user , userinfo = await oidc_handler .get_current_active_user_from_openid_token (
145
+ mock_token
146
+ )
147
+
148
+ assert user is not None
149
+ assert userinfo is not None
150
+
151
+ assert user == mock_user
152
+ assert userinfo .get ("email" ) == mock_jwt_payload .claims .get ("email" )
153
+
154
+
155
+ async def test_oidc_token_unverified_email (
156
+ mocker , mock_oidc_enabled , mock_token , mock_openid_configuration
157
+ ):
158
+ """Test token decoding for unverified email."""
159
+ mocker .patch .object (
160
+ StarletteOAuth2App ,
161
+ "load_server_metadata" ,
162
+ return_value = mock_openid_configuration ,
163
+ )
164
+
165
+ unverified_token = mock_token
166
+ unverified_token ["userinfo" ]["email_verified" ] = False
167
+
168
+ oidc_handler = OpenIDHandler ()
169
+ with pytest .raises (HTTPException ):
170
+ await oidc_handler .get_current_active_user_from_openid_token (unverified_token )
171
+
172
+
173
+ async def test_oidc_token_without_email_verified_claim (
174
+ mocker , mock_oidc_enabled , mock_token , mock_openid_configuration
175
+ ):
176
+ """Test token decoding with server not supporting email_verified claim."""
177
+ mock_jwt_payload = Token (
178
+ header = {"alg" : "RS256" },
179
+ claims = {
"iss" :
OIDC_SERVER_APPLICATION_URL ,
"email" :
"[email protected] " },
180
+ )
181
+ mock_user = MagicMock (enabled = True )
182
+ mocker .patch (
183
+ "handler.database.db_user_handler.get_user_by_email" , return_value = mock_user
184
+ )
185
+
186
+ openid_conf = mock_openid_configuration
187
+ openid_conf ["claims_supported" ] = openid_conf ["claims_supported" ].remove (
188
+ "email_verified"
189
+ )
190
+ mocker .patch .object (
191
+ StarletteOAuth2App , "load_server_metadata" , return_value = openid_conf
192
+ )
193
+
194
+ unverified_token = mock_token
195
+ del unverified_token ["userinfo" ]["email_verified" ]
73
196
74
197
oidc_handler = OpenIDHandler ()
75
198
user , userinfo = await oidc_handler .get_current_active_user_from_openid_token (
0 commit comments