@@ -113,6 +113,148 @@ def response(self, obj: TokenSuccessResponse | TokenErrorResponse):
113
113
},
114
114
)
115
115
116
+ async def _handle_authorization_code (
117
+ self , client_info : Any , token_request : AuthorizationCodeRequest
118
+ ) -> TokenSuccessResponse | TokenErrorResponse :
119
+ auth_code = await self .provider .load_authorization_code (client_info , token_request .code )
120
+ if auth_code is None or auth_code .client_id != token_request .client_id :
121
+ # if code belongs to different client, pretend it doesn't exist
122
+ return TokenErrorResponse (
123
+ error = "invalid_grant" ,
124
+ error_description = "authorization code does not exist" ,
125
+ )
126
+
127
+ # make auth codes expire after a deadline
128
+ # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.5
129
+ if auth_code .expires_at < time .time ():
130
+ return TokenErrorResponse (
131
+ error = "invalid_grant" ,
132
+ error_description = "authorization code has expired" ,
133
+ )
134
+
135
+ # verify redirect_uri doesn't change between /authorize and /tokens
136
+ # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
137
+ if auth_code .redirect_uri_provided_explicitly :
138
+ authorize_request_redirect_uri = auth_code .redirect_uri
139
+ else :
140
+ authorize_request_redirect_uri = None
141
+
142
+ # Convert both sides to strings for comparison to handle AnyUrl vs string issues
143
+ token_redirect_str = str (token_request .redirect_uri ) if token_request .redirect_uri is not None else None
144
+ auth_redirect_str = (
145
+ str (authorize_request_redirect_uri ) if authorize_request_redirect_uri is not None else None
146
+ )
147
+
148
+ if token_redirect_str != auth_redirect_str :
149
+ return TokenErrorResponse (
150
+ error = "invalid_request" ,
151
+ error_description = ("redirect_uri did not match the one used when creating auth code" ),
152
+ )
153
+
154
+ # Verify PKCE code verifier
155
+ sha256 = hashlib .sha256 (token_request .code_verifier .encode ()).digest ()
156
+ hashed_code_verifier = base64 .urlsafe_b64encode (sha256 ).decode ().rstrip ("=" )
157
+
158
+ if hashed_code_verifier != auth_code .code_challenge :
159
+ # see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
160
+ return TokenErrorResponse (
161
+ error = "invalid_grant" ,
162
+ error_description = "incorrect code_verifier" ,
163
+ )
164
+
165
+ try :
166
+ # Exchange authorization code for tokens
167
+ tokens = await self .provider .exchange_authorization_code (client_info , auth_code )
168
+ except TokenError as e :
169
+ return TokenErrorResponse (
170
+ error = e .error ,
171
+ error_description = e .error_description ,
172
+ )
173
+
174
+ return TokenSuccessResponse (root = tokens )
175
+
176
+ async def _handle_client_credentials (
177
+ self , client_info : Any , token_request : ClientCredentialsRequest
178
+ ) -> TokenSuccessResponse | TokenErrorResponse :
179
+ scopes = (
180
+ token_request .scope .split (" " )
181
+ if token_request .scope
182
+ else client_info .scope .split (" " )
183
+ if client_info .scope
184
+ else []
185
+ )
186
+ try :
187
+ tokens = await self .provider .exchange_client_credentials (client_info , scopes )
188
+ except TokenError as e :
189
+ return TokenErrorResponse (
190
+ error = e .error ,
191
+ error_description = e .error_description ,
192
+ )
193
+
194
+ return TokenSuccessResponse (root = tokens )
195
+
196
+ async def _handle_token_exchange (
197
+ self , client_info : Any , token_request : TokenExchangeRequest
198
+ ) -> TokenSuccessResponse | TokenErrorResponse :
199
+ scopes = token_request .scope .split (" " ) if token_request .scope else []
200
+ try :
201
+ tokens = await self .provider .exchange_token (
202
+ client_info ,
203
+ token_request .subject_token ,
204
+ token_request .subject_token_type ,
205
+ token_request .actor_token ,
206
+ token_request .actor_token_type ,
207
+ scopes ,
208
+ token_request .audience ,
209
+ token_request .resource ,
210
+ )
211
+ except TokenError as e :
212
+ return TokenErrorResponse (
213
+ error = e .error ,
214
+ error_description = e .error_description ,
215
+ )
216
+
217
+ return TokenSuccessResponse (root = tokens )
218
+
219
+ async def _handle_refresh_token (
220
+ self , client_info : Any , token_request : RefreshTokenRequest
221
+ ) -> TokenSuccessResponse | TokenErrorResponse :
222
+ refresh_token = await self .provider .load_refresh_token (client_info , token_request .refresh_token )
223
+ if refresh_token is None or refresh_token .client_id != token_request .client_id :
224
+ # if token belongs to a different client, pretend it doesn't exist
225
+ return TokenErrorResponse (
226
+ error = "invalid_grant" ,
227
+ error_description = "refresh token does not exist" ,
228
+ )
229
+
230
+ if refresh_token .expires_at and refresh_token .expires_at < time .time ():
231
+ # if the refresh token has expired, pretend it doesn't exist
232
+ return TokenErrorResponse (
233
+ error = "invalid_grant" ,
234
+ error_description = "refresh token has expired" ,
235
+ )
236
+
237
+ # Parse scopes if provided
238
+ scopes = token_request .scope .split (" " ) if token_request .scope else refresh_token .scopes
239
+
240
+ for scope in scopes :
241
+ if scope not in refresh_token .scopes :
242
+ return TokenErrorResponse (
243
+ error = "invalid_scope" ,
244
+ error_description = (f"cannot request scope `{ scope } ` not provided by refresh token" ),
245
+ )
246
+
247
+ try :
248
+ # Exchange refresh token for new tokens
249
+ tokens = await self .provider .exchange_refresh_token (client_info , refresh_token , scopes )
250
+ except TokenError as e :
251
+ return TokenErrorResponse (
252
+ error = e .error ,
253
+ error_description = e .error_description ,
254
+ )
255
+
256
+ return TokenSuccessResponse (root = tokens )
257
+
116
258
async def handle (self , request : Request ):
117
259
try :
118
260
form_data = await request .form ()
@@ -146,155 +288,17 @@ async def handle(self, request: Request):
146
288
)
147
289
)
148
290
149
- tokens : OAuthToken
150
-
151
291
match token_request :
152
292
case AuthorizationCodeRequest ():
153
- auth_code = await self .provider .load_authorization_code (client_info , token_request .code )
154
- if auth_code is None or auth_code .client_id != token_request .client_id :
155
- # if code belongs to different client, pretend it doesn't exist
156
- return self .response (
157
- TokenErrorResponse (
158
- error = "invalid_grant" ,
159
- error_description = "authorization code does not exist" ,
160
- )
161
- )
162
-
163
- # make auth codes expire after a deadline
164
- # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.5
165
- if auth_code .expires_at < time .time ():
166
- return self .response (
167
- TokenErrorResponse (
168
- error = "invalid_grant" ,
169
- error_description = "authorization code has expired" ,
170
- )
171
- )
172
-
173
- # verify redirect_uri doesn't change between /authorize and /tokens
174
- # see https://datatracker.ietf.org/doc/html/rfc6749#section-10.6
175
- if auth_code .redirect_uri_provided_explicitly :
176
- authorize_request_redirect_uri = auth_code .redirect_uri
177
- else :
178
- authorize_request_redirect_uri = None
179
-
180
- # Convert both sides to strings for comparison to handle AnyUrl vs string issues
181
- token_redirect_str = str (token_request .redirect_uri ) if token_request .redirect_uri is not None else None
182
- auth_redirect_str = (
183
- str (authorize_request_redirect_uri ) if authorize_request_redirect_uri is not None else None
184
- )
185
-
186
- if token_redirect_str != auth_redirect_str :
187
- return self .response (
188
- TokenErrorResponse (
189
- error = "invalid_request" ,
190
- error_description = ("redirect_uri did not match the one used when creating auth code" ),
191
- )
192
- )
193
-
194
- # Verify PKCE code verifier
195
- sha256 = hashlib .sha256 (token_request .code_verifier .encode ()).digest ()
196
- hashed_code_verifier = base64 .urlsafe_b64encode (sha256 ).decode ().rstrip ("=" )
197
-
198
- if hashed_code_verifier != auth_code .code_challenge :
199
- # see https://datatracker.ietf.org/doc/html/rfc7636#section-4.6
200
- return self .response (
201
- TokenErrorResponse (
202
- error = "invalid_grant" ,
203
- error_description = "incorrect code_verifier" ,
204
- )
205
- )
206
-
207
- try :
208
- # Exchange authorization code for tokens
209
- tokens = await self .provider .exchange_authorization_code (client_info , auth_code )
210
- except TokenError as e :
211
- return self .response (
212
- TokenErrorResponse (
213
- error = e .error ,
214
- error_description = e .error_description ,
215
- )
216
- )
293
+ result = await self ._handle_authorization_code (client_info , token_request )
217
294
218
295
case ClientCredentialsRequest ():
219
- scopes = (
220
- token_request .scope .split (" " )
221
- if token_request .scope
222
- else client_info .scope .split (" " )
223
- if client_info .scope
224
- else []
225
- )
226
- try :
227
- tokens = await self .provider .exchange_client_credentials (client_info , scopes )
228
- except TokenError as e :
229
- return self .response (
230
- TokenErrorResponse (
231
- error = e .error ,
232
- error_description = e .error_description ,
233
- )
234
- )
296
+ result = await self ._handle_client_credentials (client_info , token_request )
235
297
236
298
case TokenExchangeRequest ():
237
- scopes = token_request .scope .split (" " ) if token_request .scope else []
238
- try :
239
- tokens = await self .provider .exchange_token (
240
- client_info ,
241
- token_request .subject_token ,
242
- token_request .subject_token_type ,
243
- token_request .actor_token ,
244
- token_request .actor_token_type ,
245
- scopes ,
246
- token_request .audience ,
247
- token_request .resource ,
248
- )
249
- except TokenError as e :
250
- return self .response (
251
- TokenErrorResponse (
252
- error = e .error ,
253
- error_description = e .error_description ,
254
- )
255
- )
299
+ result = await self ._handle_token_exchange (client_info , token_request )
256
300
257
301
case RefreshTokenRequest ():
258
- refresh_token = await self .provider .load_refresh_token (client_info , token_request .refresh_token )
259
- if refresh_token is None or refresh_token .client_id != token_request .client_id :
260
- # if token belongs to a different client, pretend it doesn't exist
261
- return self .response (
262
- TokenErrorResponse (
263
- error = "invalid_grant" ,
264
- error_description = "refresh token does not exist" ,
265
- )
266
- )
267
-
268
- if refresh_token .expires_at and refresh_token .expires_at < time .time ():
269
- # if the refresh token has expired, pretend it doesn't exist
270
- return self .response (
271
- TokenErrorResponse (
272
- error = "invalid_grant" ,
273
- error_description = "refresh token has expired" ,
274
- )
275
- )
276
-
277
- # Parse scopes if provided
278
- scopes = token_request .scope .split (" " ) if token_request .scope else refresh_token .scopes
279
-
280
- for scope in scopes :
281
- if scope not in refresh_token .scopes :
282
- return self .response (
283
- TokenErrorResponse (
284
- error = "invalid_scope" ,
285
- error_description = (f"cannot request scope `{ scope } ` not provided by refresh token" ),
286
- )
287
- )
288
-
289
- try :
290
- # Exchange refresh token for new tokens
291
- tokens = await self .provider .exchange_refresh_token (client_info , refresh_token , scopes )
292
- except TokenError as e :
293
- return self .response (
294
- TokenErrorResponse (
295
- error = e .error ,
296
- error_description = e .error_description ,
297
- )
298
- )
299
-
300
- return self .response (TokenSuccessResponse (root = tokens ))
302
+ result = await self ._handle_refresh_token (client_info , token_request )
303
+
304
+ return self .response (result )
0 commit comments