c2cgeoportal-geoportal 2.7.1.156__py2.py3-none-any.whl → 2.8.1.180__py2.py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- c2cgeoportal_geoportal/__init__.py +24 -14
- c2cgeoportal_geoportal/lib/authentication.py +10 -14
- c2cgeoportal_geoportal/lib/caching.py +8 -6
- c2cgeoportal_geoportal/lib/checker.py +10 -6
- c2cgeoportal_geoportal/lib/common_headers.py +5 -8
- c2cgeoportal_geoportal/lib/dbreflection.py +8 -8
- c2cgeoportal_geoportal/lib/filter_capabilities.py +5 -1
- c2cgeoportal_geoportal/lib/lingua_extractor.py +11 -12
- c2cgeoportal_geoportal/lib/loader.py +1 -1
- c2cgeoportal_geoportal/lib/oauth2.py +217 -100
- c2cgeoportal_geoportal/lib/wmstparsing.py +8 -12
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/Dockerfile +9 -11
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/development.ini +1 -1
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/gunicorn.conf.py +0 -2
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/requirements.txt +1 -1
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.api.js +6 -4
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.apps.js +1 -3
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/webpack.commons.js +1 -0
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/{{cookiecutter.package}}_geoportal/__init__.py +1 -6
- c2cgeoportal_geoportal/scaffolds/advance_update/{{cookiecutter.project}}/geoportal/CONST_Makefile +0 -20
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/main.yaml +20 -6
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/.github/workflows/update_l10n.yaml +4 -3
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Dockerfile +22 -22
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/Makefile +58 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/build +48 -24
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/config.yaml +2 -5
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/docker-compose-check +25 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/ci/requirements.txt +1 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-db.yaml +26 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-lib.yaml +53 -26
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose-qgis.yaml +23 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.override.sample.yaml +0 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/docker-compose.yaml +3 -3
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.default +21 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/env.project +9 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/geoportal/vars.yaml +38 -14
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/data/Readme.txt +2 -2
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.conf +15 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/mapserver/mapserver.map.tmpl +2 -3
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Landscape.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A3_Portrait.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Landscape.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/A4_Portrait.jrxml +5 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/print/print-apps/{{cookiecutter.package}}/config.yaml.tmpl +6 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/pyproject.toml +4 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/run_alembic.sh +3 -5
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-backup +1 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/scripts/db-restore +1 -1
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/spell-ignore-words.txt +2 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/__init__.py +0 -0
- c2cgeoportal_geoportal/scaffolds/create/{{cookiecutter.project}}/tests/test_app.py +38 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/.upgrade.yaml +2 -132
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_CHANGELOG.txt +210 -1097
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/CONST_create_template/tests/test_testapp.py +48 -0
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_config-schema.yaml +17 -15
- c2cgeoportal_geoportal/scaffolds/update/{{cookiecutter.project}}/geoportal/CONST_vars.yaml +46 -2
- c2cgeoportal_geoportal/scripts/c2cupgrade.py +1 -2
- c2cgeoportal_geoportal/scripts/pcreate.py +8 -10
- c2cgeoportal_geoportal/scripts/theme2fts.py +58 -3
- c2cgeoportal_geoportal/views/__init__.py +1 -3
- c2cgeoportal_geoportal/views/dynamic.py +1 -1
- c2cgeoportal_geoportal/views/entry.py +2 -10
- c2cgeoportal_geoportal/views/fulltextsearch.py +1 -1
- c2cgeoportal_geoportal/views/geometry_processing.py +3 -3
- c2cgeoportal_geoportal/views/layers.py +10 -11
- c2cgeoportal_geoportal/views/login.py +63 -8
- c2cgeoportal_geoportal/views/mapserverproxy.py +2 -3
- c2cgeoportal_geoportal/views/ogcproxy.py +6 -2
- c2cgeoportal_geoportal/views/pdfreport.py +4 -4
- c2cgeoportal_geoportal/views/printproxy.py +2 -2
- c2cgeoportal_geoportal/views/profile.py +1 -1
- c2cgeoportal_geoportal/views/proxy.py +2 -4
- c2cgeoportal_geoportal/views/raster.py +2 -2
- c2cgeoportal_geoportal/views/resourceproxy.py +1 -1
- c2cgeoportal_geoportal/views/shortener.py +1 -2
- c2cgeoportal_geoportal/views/theme.py +97 -63
- c2cgeoportal_geoportal/views/tinyowsproxy.py +3 -12
- c2cgeoportal_geoportal/views/vector_tiles.py +1 -1
- {c2cgeoportal_geoportal-2.7.1.156.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/METADATA +21 -15
- {c2cgeoportal_geoportal-2.7.1.156.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/RECORD +96 -90
- {c2cgeoportal_geoportal-2.7.1.156.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/entry_points.txt +1 -0
- tests/__init__.py +3 -2
- tests/test_cachebuster.py +3 -3
- tests/test_caching.py +7 -7
- tests/test_checker.py +1 -1
- tests/test_decimaljson.py +1 -1
- tests/test_headerstween.py +1 -1
- tests/test_i18n.py +1 -1
- tests/test_init.py +14 -15
- tests/test_locale_negociator.py +4 -4
- tests/test_mapserverproxy_route_predicate.py +1 -2
- tests/test_raster.py +15 -15
- tests/test_wmstparsing.py +10 -10
- tests/xmlstr.py +1 -3
- c2cgeoportal_geoportal/scaffolds/advance_create/{{cookiecutter.project}}/geoportal/tools/extract-messages.js +0 -41
- {c2cgeoportal_geoportal-2.7.1.156.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/WHEEL +0 -0
- {c2cgeoportal_geoportal-2.7.1.156.dist-info → c2cgeoportal_geoportal-2.8.1.180.dist-info}/top_level.txt +0 -0
@@ -27,14 +27,15 @@
|
|
27
27
|
|
28
28
|
import logging
|
29
29
|
from datetime import datetime, timedelta
|
30
|
-
from typing import Any, Dict, List, Union
|
30
|
+
from typing import Any, Dict, List, Optional, Union
|
31
31
|
|
32
32
|
import basicauth
|
33
33
|
import oauthlib.common
|
34
34
|
import oauthlib.oauth2
|
35
35
|
import pyramid.threadlocal
|
36
|
+
from pyramid.httpexceptions import HTTPBadRequest
|
36
37
|
|
37
|
-
import c2cgeoportal_commons
|
38
|
+
import c2cgeoportal_commons
|
38
39
|
from c2cgeoportal_geoportal.lib.caching import get_region
|
39
40
|
|
40
41
|
LOG = logging.getLogger(__name__)
|
@@ -48,7 +49,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
48
49
|
# in minutes
|
49
50
|
self.authorization_expires_in = authorization_expires_in
|
50
51
|
|
51
|
-
def authenticate_client(
|
52
|
+
def authenticate_client(
|
52
53
|
self,
|
53
54
|
request: oauthlib.common.Request,
|
54
55
|
*args: Any,
|
@@ -65,7 +66,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
65
66
|
both body and query can be obtained by direct attribute access, i.e.
|
66
67
|
request.client_id for client_id in the URL query.
|
67
68
|
|
68
|
-
Arguments:
|
69
|
+
Keyword Arguments:
|
69
70
|
|
70
71
|
request: oauthlib.common.Request
|
71
72
|
|
@@ -85,7 +86,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
85
86
|
|
86
87
|
raise NotImplementedError("Not implemented, the method `authenticate_client_id` should be used.")
|
87
88
|
|
88
|
-
def authenticate_client_id(
|
89
|
+
def authenticate_client_id(
|
89
90
|
self,
|
90
91
|
client_id: str,
|
91
92
|
request: oauthlib.common.Request,
|
@@ -133,7 +134,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
133
134
|
LOG.debug("authenticate_client_id => %s", request.client is not None)
|
134
135
|
return request.client is not None
|
135
136
|
|
136
|
-
def client_authentication_required(
|
137
|
+
def client_authentication_required(
|
137
138
|
self,
|
138
139
|
request: oauthlib.common.Request,
|
139
140
|
*args: Any,
|
@@ -153,7 +154,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
153
154
|
client credentials or whenever Client provided client authentication, see
|
154
155
|
`Section 6`_
|
155
156
|
|
156
|
-
Arguments:
|
157
|
+
Keyword Arguments:
|
157
158
|
|
158
159
|
request: oauthlib.common.Request
|
159
160
|
|
@@ -174,7 +175,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
174
175
|
|
175
176
|
return False
|
176
177
|
|
177
|
-
def confirm_redirect_uri(
|
178
|
+
def confirm_redirect_uri(
|
178
179
|
self,
|
179
180
|
client_id: str,
|
180
181
|
code: str,
|
@@ -195,7 +196,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
195
196
|
the client's allowed redirect URIs, but against the URI used when the
|
196
197
|
code was saved.
|
197
198
|
|
198
|
-
Arguments:
|
199
|
+
Keyword Arguments:
|
199
200
|
|
200
201
|
client_id: Unicode client identifier
|
201
202
|
code: Unicode authorization_code.
|
@@ -226,33 +227,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
226
227
|
LOG.debug("confirm_redirect_uri => %s", authorization_code is not None)
|
227
228
|
return authorization_code is not None
|
228
229
|
|
229
|
-
def
|
230
|
-
self, code: str, request: oauthlib.common.Request
|
231
|
-
) -> None:
|
232
|
-
"""
|
233
|
-
Is called during the "token" request processing.
|
234
|
-
|
235
|
-
When a ``code_verifier`` and a ``code_challenge`` has
|
236
|
-
been provided. See ``.get_code_challenge``. Must return ``plain`` or ``S256``. You can return a custom
|
237
|
-
value if you have implemented your own ``AuthorizationCodeGrant`` class.
|
238
|
-
|
239
|
-
Arguments:
|
240
|
-
|
241
|
-
code: Authorization code.
|
242
|
-
request: OAuthlib request.
|
243
|
-
|
244
|
-
Returns: code_challenge_method string
|
245
|
-
|
246
|
-
Method is used by:
|
247
|
-
- Authorization Code Grant - when PKCE is active
|
248
|
-
"""
|
249
|
-
del code, request
|
250
|
-
|
251
|
-
LOG.debug("get_code_challenge_method")
|
252
|
-
|
253
|
-
raise NotImplementedError("Not implemented.")
|
254
|
-
|
255
|
-
def get_default_redirect_uri( # pylint: disable=no-self-use,useless-suppression
|
230
|
+
def get_default_redirect_uri(
|
256
231
|
self,
|
257
232
|
client_id: str,
|
258
233
|
request: oauthlib.common.Request,
|
@@ -262,7 +237,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
262
237
|
"""
|
263
238
|
Get the default redirect URI for the client.
|
264
239
|
|
265
|
-
Arguments:
|
240
|
+
Keyword Arguments:
|
266
241
|
|
267
242
|
client_id: Unicode client identifier
|
268
243
|
request: The HTTP Request
|
@@ -279,7 +254,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
279
254
|
|
280
255
|
raise NotImplementedError("Not implemented.")
|
281
256
|
|
282
|
-
def get_default_scopes(
|
257
|
+
def get_default_scopes(
|
283
258
|
self,
|
284
259
|
client_id: str,
|
285
260
|
request: oauthlib.common.Request,
|
@@ -289,7 +264,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
289
264
|
"""
|
290
265
|
Get the default scopes for the client.
|
291
266
|
|
292
|
-
Arguments:
|
267
|
+
Keyword Arguments:
|
293
268
|
|
294
269
|
client_id: Unicode client identifier
|
295
270
|
request: The HTTP Request
|
@@ -308,7 +283,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
308
283
|
|
309
284
|
return ["geomapfish"]
|
310
285
|
|
311
|
-
def get_original_scopes(
|
286
|
+
def get_original_scopes(
|
312
287
|
self,
|
313
288
|
refresh_token: str,
|
314
289
|
request: oauthlib.common.Request,
|
@@ -318,7 +293,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
318
293
|
"""
|
319
294
|
Get the list of scopes associated with the refresh token.
|
320
295
|
|
321
|
-
Arguments:
|
296
|
+
Keyword Arguments:
|
322
297
|
|
323
298
|
refresh_token: Unicode refresh token
|
324
299
|
request: The HTTP Request
|
@@ -334,7 +309,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
334
309
|
|
335
310
|
return []
|
336
311
|
|
337
|
-
def introspect_token(
|
312
|
+
def introspect_token(
|
338
313
|
self,
|
339
314
|
token: str,
|
340
315
|
token_type_hint: str,
|
@@ -366,7 +341,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
366
341
|
efficiency, but must fallback to other types to be compliant with RFC.
|
367
342
|
The dict of claims is added to request.token after this method.
|
368
343
|
|
369
|
-
Arguments:
|
344
|
+
Keyword Arguments:
|
370
345
|
|
371
346
|
token: The token string.
|
372
347
|
token_type_hint: access_token or refresh_token.
|
@@ -384,7 +359,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
384
359
|
|
385
360
|
raise NotImplementedError("Not implemented.")
|
386
361
|
|
387
|
-
def invalidate_authorization_code(
|
362
|
+
def invalidate_authorization_code(
|
388
363
|
self,
|
389
364
|
client_id: str,
|
390
365
|
code: str,
|
@@ -395,7 +370,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
395
370
|
"""
|
396
371
|
Invalidate an authorization code after use.
|
397
372
|
|
398
|
-
Arguments:
|
373
|
+
Keyword Arguments:
|
399
374
|
|
400
375
|
client_id: Unicode client identifier
|
401
376
|
code: The authorization code grant (request.code).
|
@@ -419,7 +394,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
419
394
|
.one()
|
420
395
|
)
|
421
396
|
|
422
|
-
def is_within_original_scope(
|
397
|
+
def is_within_original_scope(
|
423
398
|
self,
|
424
399
|
request_scopes: List[str],
|
425
400
|
refresh_token: str,
|
@@ -438,7 +413,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
438
413
|
used in situations where returning all valid scopes from the
|
439
414
|
get_original_scopes is not practical.
|
440
415
|
|
441
|
-
Arguments:
|
416
|
+
Keyword Arguments:
|
442
417
|
|
443
418
|
request_scopes: A list of scopes that were requested by client
|
444
419
|
refresh_token: Unicode refresh_token
|
@@ -453,7 +428,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
453
428
|
|
454
429
|
return False
|
455
430
|
|
456
|
-
def revoke_token(
|
431
|
+
def revoke_token(
|
457
432
|
self,
|
458
433
|
token: str,
|
459
434
|
token_type_hint: str,
|
@@ -464,7 +439,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
464
439
|
"""
|
465
440
|
Revoke an access or refresh token.
|
466
441
|
|
467
|
-
Arguments:
|
442
|
+
Keyword Arguments:
|
468
443
|
|
469
444
|
token: The token string.
|
470
445
|
token_type_hint: access_token or refresh_token.
|
@@ -479,9 +454,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
479
454
|
|
480
455
|
raise NotImplementedError("Not implemented.")
|
481
456
|
|
482
|
-
def rotate_refresh_token(
|
483
|
-
self, request: oauthlib.common.Request
|
484
|
-
) -> bool:
|
457
|
+
def rotate_refresh_token(self, request: oauthlib.common.Request) -> bool:
|
485
458
|
"""
|
486
459
|
Determine whether to rotate the refresh token. Default, yes.
|
487
460
|
|
@@ -489,7 +462,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
489
462
|
or replaced with a new one (rotated). Return True to rotate and
|
490
463
|
and False for keeping original.
|
491
464
|
|
492
|
-
Arguments:
|
465
|
+
Keyword Arguments:
|
493
466
|
|
494
467
|
request: oauthlib.common.Request
|
495
468
|
|
@@ -502,7 +475,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
502
475
|
|
503
476
|
return True
|
504
477
|
|
505
|
-
def save_authorization_code(
|
478
|
+
def save_authorization_code(
|
506
479
|
self,
|
507
480
|
client_id: str,
|
508
481
|
code: Dict[str, str],
|
@@ -523,13 +496,13 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
523
496
|
The 'code' argument is actually a dictionary, containing at least a
|
524
497
|
'code' key with the actual authorization code:
|
525
498
|
|
526
|
-
{'code': '
|
499
|
+
{'code': '<secret>'}
|
527
500
|
|
528
501
|
It may also have a 'state' key containing a nonce for the client, if it
|
529
502
|
chose to send one. That value should be saved and used in
|
530
503
|
'validate_code'.
|
531
504
|
|
532
|
-
Arguments:
|
505
|
+
Keyword Arguments:
|
533
506
|
|
534
507
|
client_id: Unicode client identifier
|
535
508
|
code: A dict of the authorization code grant and, optionally, state.
|
@@ -537,6 +510,11 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
537
510
|
|
538
511
|
Method is used by:
|
539
512
|
- Authorization Code Grant
|
513
|
+
|
514
|
+
To support PKCE, you MUST associate the code with:
|
515
|
+
|
516
|
+
Code Challenge (request.code_challenge) and
|
517
|
+
Code Challenge Method (request.code_challenge_method)
|
540
518
|
"""
|
541
519
|
del args, kwargs
|
542
520
|
|
@@ -544,7 +522,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
544
522
|
|
545
523
|
from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
|
546
524
|
|
547
|
-
user = pyramid.threadlocal.get_current_request().
|
525
|
+
user = pyramid.threadlocal.get_current_request().user
|
548
526
|
|
549
527
|
# Don't allows to have two authentications for the same user and the same client
|
550
528
|
authorization_code = (
|
@@ -555,20 +533,32 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
555
533
|
)
|
556
534
|
|
557
535
|
if authorization_code is not None:
|
558
|
-
authorization_code.code = code["code"]
|
559
536
|
authorization_code.expire_at = datetime.now() + timedelta(minutes=self.authorization_expires_in)
|
560
|
-
authorization_code.redirect_uri = request.redirect_uri
|
561
537
|
else:
|
562
538
|
authorization_code = static.OAuth2AuthorizationCode()
|
563
539
|
authorization_code.client_id = request.client.id
|
564
|
-
authorization_code.code = code["code"]
|
565
540
|
authorization_code.user_id = user.id
|
566
541
|
authorization_code.expire_at = datetime.now() + timedelta(minutes=self.authorization_expires_in)
|
567
|
-
authorization_code.
|
542
|
+
authorization_code.state = code.get("state")
|
543
|
+
|
544
|
+
authorization_code.code = code["code"]
|
545
|
+
authorization_code.redirect_uri = request.redirect_uri
|
546
|
+
|
547
|
+
client = (
|
548
|
+
DBSession.query(static.OAuth2Client)
|
549
|
+
.filter(static.OAuth2Client.client_id == client_id)
|
550
|
+
.one_or_none()
|
551
|
+
)
|
552
|
+
if client and client.state_required and not code.get("state"):
|
553
|
+
raise HTTPBadRequest("Client is missing the state parameter.")
|
554
|
+
|
555
|
+
if client and client.pkce_required:
|
556
|
+
authorization_code.challenge = request.code_challenge
|
557
|
+
authorization_code.challenge_method = request.code_challenge_method
|
568
558
|
|
569
559
|
DBSession.add(authorization_code)
|
570
560
|
|
571
|
-
def save_bearer_token(
|
561
|
+
def save_bearer_token(
|
572
562
|
self,
|
573
563
|
token: Dict[str, Union[str, int]],
|
574
564
|
request: oauthlib.common.Request,
|
@@ -589,17 +579,17 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
589
579
|
|
590
580
|
{
|
591
581
|
'token_type': 'Bearer',
|
592
|
-
'access_token': '
|
582
|
+
'access_token': '<secret>',
|
593
583
|
'expires_in': 3600,
|
594
584
|
'scope': 'string of space separated authorized scopes',
|
595
|
-
'refresh_token': '
|
585
|
+
'refresh_token': '<secret>', # if issued
|
596
586
|
'state': 'given_by_client', # if supplied by client
|
597
587
|
}
|
598
588
|
|
599
589
|
Note that while "scope" is a string-separated list of authorized scopes,
|
600
590
|
the original list is still available in request.scopes
|
601
591
|
|
602
|
-
Arguments:
|
592
|
+
Keyword Arguments:
|
603
593
|
|
604
594
|
client_id: Unicode client identifier
|
605
595
|
token: A Bearer token dict
|
@@ -627,21 +617,18 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
627
617
|
.one_or_none()
|
628
618
|
)
|
629
619
|
|
630
|
-
if bearer_token is
|
631
|
-
bearer_token.access_token = token["access_token"]
|
632
|
-
bearer_token.refresh_token = token["refresh_token"]
|
633
|
-
bearer_token.expire_at = datetime.now() + timedelta(seconds=float(token["expires_in"]))
|
634
|
-
else:
|
620
|
+
if bearer_token is None:
|
635
621
|
bearer_token = static.OAuth2BearerToken()
|
636
622
|
bearer_token.client_id = request.client.id
|
637
623
|
bearer_token.user_id = request.user.id
|
638
|
-
bearer_token.access_token = token["access_token"]
|
639
|
-
bearer_token.refresh_token = token["refresh_token"]
|
640
|
-
bearer_token.expire_at = datetime.now() + timedelta(seconds=float(token["expires_in"]))
|
641
|
-
|
642
624
|
DBSession.add(bearer_token)
|
643
625
|
|
644
|
-
|
626
|
+
bearer_token.access_token = token["access_token"]
|
627
|
+
bearer_token.refresh_token = token["refresh_token"]
|
628
|
+
bearer_token.expire_at = datetime.now() + timedelta(seconds=float(token["expires_in"]))
|
629
|
+
bearer_token.state = token.get("state")
|
630
|
+
|
631
|
+
def validate_bearer_token(
|
645
632
|
self,
|
646
633
|
token: str,
|
647
634
|
scopes: List[str],
|
@@ -650,7 +637,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
650
637
|
"""
|
651
638
|
Ensure the Bearer token is valid and authorized access to scopes.
|
652
639
|
|
653
|
-
Arguments:
|
640
|
+
Keyword Arguments:
|
654
641
|
|
655
642
|
token: A string of random characters.
|
656
643
|
scopes: A list of scopes associated with the protected resource.
|
@@ -686,7 +673,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
686
673
|
one provided for django these attributes will be made available
|
687
674
|
in all protected views as keyword arguments.
|
688
675
|
|
689
|
-
Arguments:
|
676
|
+
Keyword Arguments:
|
690
677
|
|
691
678
|
token: Unicode Bearer token
|
692
679
|
scopes: List of scopes (defined by you)
|
@@ -705,6 +692,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
705
692
|
|
706
693
|
bearer_token = (
|
707
694
|
DBSession.query(static.OAuth2BearerToken)
|
695
|
+
.join(static.User)
|
708
696
|
.filter(static.OAuth2BearerToken.access_token == token)
|
709
697
|
.filter(static.OAuth2BearerToken.expire_at > datetime.now())
|
710
698
|
).one_or_none()
|
@@ -715,7 +703,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
715
703
|
LOG.debug("validate_bearer_token => %s", bearer_token is not None)
|
716
704
|
return bearer_token is not None
|
717
705
|
|
718
|
-
def validate_client_id(
|
706
|
+
def validate_client_id(
|
719
707
|
self,
|
720
708
|
client_id: str,
|
721
709
|
request: oauthlib.common.Request,
|
@@ -729,7 +717,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
729
717
|
to set request.client to the client object associated with the
|
730
718
|
given client_id.
|
731
719
|
|
732
|
-
Arguments:
|
720
|
+
Keyword Arguments:
|
733
721
|
|
734
722
|
client_id: Unicode client identifier
|
735
723
|
request: oauthlib.common.Request
|
@@ -753,7 +741,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
753
741
|
request.client = client
|
754
742
|
return client is not None
|
755
743
|
|
756
|
-
def validate_code(
|
744
|
+
def validate_code(
|
757
745
|
self,
|
758
746
|
client_id: str,
|
759
747
|
code: str,
|
@@ -775,7 +763,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
775
763
|
associated with this authorization code. Similarly request.scopes
|
776
764
|
must also be set.
|
777
765
|
|
778
|
-
Arguments:
|
766
|
+
Keyword Arguments:
|
779
767
|
|
780
768
|
client_id: Unicode client identifier
|
781
769
|
code: Unicode authorization code
|
@@ -791,20 +779,32 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
791
779
|
|
792
780
|
from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
|
793
781
|
|
794
|
-
|
782
|
+
authorization_code_query = (
|
795
783
|
DBSession.query(static.OAuth2AuthorizationCode)
|
796
784
|
.join(static.OAuth2AuthorizationCode.client)
|
797
785
|
.filter(static.OAuth2AuthorizationCode.code == code)
|
798
|
-
.filter(static.
|
786
|
+
.filter(static.OAuth2AuthorizationCode.client_id == client.id)
|
799
787
|
.filter(static.OAuth2AuthorizationCode.expire_at > datetime.now())
|
800
|
-
.one_or_none()
|
801
788
|
)
|
802
|
-
if
|
803
|
-
|
804
|
-
|
805
|
-
|
789
|
+
if client.state_required:
|
790
|
+
authorization_code_query = authorization_code_query.filter(
|
791
|
+
static.OAuth2AuthorizationCode.state == request.state
|
792
|
+
)
|
793
|
+
|
794
|
+
authorization_code = authorization_code_query.one_or_none()
|
795
|
+
if authorization_code is None:
|
796
|
+
LOG.debug("validate_code => KO, no authorization_code found")
|
797
|
+
return False
|
806
798
|
|
807
|
-
|
799
|
+
if authorization_code.client.pkce_required:
|
800
|
+
request.code_challenge = authorization_code.challenge
|
801
|
+
request.code_challenge_method = authorization_code.challenge_method
|
802
|
+
|
803
|
+
request.user = authorization_code.user
|
804
|
+
LOG.debug("validate_code => OK")
|
805
|
+
return True
|
806
|
+
|
807
|
+
def validate_grant_type(
|
808
808
|
self,
|
809
809
|
client_id: str,
|
810
810
|
grant_type: str,
|
@@ -816,7 +816,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
816
816
|
"""
|
817
817
|
Ensure client is authorized to use the grant_type requested.
|
818
818
|
|
819
|
-
Arguments:
|
819
|
+
Keyword Arguments:
|
820
820
|
|
821
821
|
client_id: Unicode client identifier
|
822
822
|
grant_type: Unicode grant type, i.e. authorization_code, password.
|
@@ -840,7 +840,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
840
840
|
|
841
841
|
return grant_type in ("authorization_code", "refresh_token")
|
842
842
|
|
843
|
-
def validate_redirect_uri(
|
843
|
+
def validate_redirect_uri(
|
844
844
|
self,
|
845
845
|
client_id: str,
|
846
846
|
redirect_uri: str,
|
@@ -854,7 +854,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
854
854
|
All clients should register the absolute URIs of all URIs they intend
|
855
855
|
to redirect to. The registration is outside of the scope of oauthlib.
|
856
856
|
|
857
|
-
Arguments:
|
857
|
+
Keyword Arguments:
|
858
858
|
|
859
859
|
client_id: Unicode client identifier
|
860
860
|
redirect_uri: Unicode absolute URI
|
@@ -879,7 +879,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
879
879
|
LOG.debug("validate_redirect_uri %s", client is not None)
|
880
880
|
return client is not None
|
881
881
|
|
882
|
-
def validate_refresh_token(
|
882
|
+
def validate_refresh_token(
|
883
883
|
self,
|
884
884
|
refresh_token: str,
|
885
885
|
client: "c2cgeoportal_commons.models.static.OAuth2Client", # noqa: F821
|
@@ -893,7 +893,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
893
893
|
OBS! The request.user attribute should be set to the resource owner
|
894
894
|
associated with this refresh token.
|
895
895
|
|
896
|
-
Arguments:
|
896
|
+
Keyword Arguments:
|
897
897
|
|
898
898
|
refresh_token: Unicode refresh token
|
899
899
|
client: Client object set by you, see authenticate_client.
|
@@ -922,7 +922,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
922
922
|
|
923
923
|
return bearer_token is not None
|
924
924
|
|
925
|
-
def validate_response_type(
|
925
|
+
def validate_response_type(
|
926
926
|
self,
|
927
927
|
client_id: str,
|
928
928
|
response_type: str,
|
@@ -934,7 +934,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
934
934
|
"""
|
935
935
|
Ensure client is authorized to use the response_type requested.
|
936
936
|
|
937
|
-
Arguments:
|
937
|
+
Keyword Arguments:
|
938
938
|
|
939
939
|
client_id: Unicode client identifier
|
940
940
|
response_type: Unicode response type, i.e. code, token.
|
@@ -951,7 +951,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
951
951
|
|
952
952
|
return response_type == "code"
|
953
953
|
|
954
|
-
def validate_scopes(
|
954
|
+
def validate_scopes(
|
955
955
|
self,
|
956
956
|
client_id: str,
|
957
957
|
scopes: List[str],
|
@@ -963,7 +963,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
963
963
|
"""
|
964
964
|
Ensure the client is authorized access to requested scopes.
|
965
965
|
|
966
|
-
Arguments:
|
966
|
+
Keyword Arguments:
|
967
967
|
|
968
968
|
client_id: Unicode client identifier
|
969
969
|
scopes: List of scopes (defined by you)
|
@@ -982,7 +982,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
982
982
|
|
983
983
|
return True
|
984
984
|
|
985
|
-
def validate_user(
|
985
|
+
def validate_user(
|
986
986
|
self,
|
987
987
|
username: str,
|
988
988
|
password: str,
|
@@ -999,7 +999,7 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
999
999
|
not set you will be unable to associate a token with a user in the
|
1000
1000
|
persistence method used (commonly, save_bearer_token).
|
1001
1001
|
|
1002
|
-
Arguments:
|
1002
|
+
Keyword Arguments:
|
1003
1003
|
|
1004
1004
|
username: Unicode username
|
1005
1005
|
password: Unicode password
|
@@ -1015,6 +1015,123 @@ class RequestValidator(oauthlib.oauth2.RequestValidator): # type: ignore
|
|
1015
1015
|
|
1016
1016
|
raise NotImplementedError("Not implemented.")
|
1017
1017
|
|
1018
|
+
def is_pkce_required(self, client_id: int, request: oauthlib.common.Request) -> bool:
|
1019
|
+
"""
|
1020
|
+
Determine if current request requires PKCE.
|
1021
|
+
|
1022
|
+
Default, False. This is called for both “authorization” and “token” requests.
|
1023
|
+
|
1024
|
+
Override this method by return True to enable PKCE for everyone. You might want to enable it only
|
1025
|
+
for public clients. Note that PKCE can also be used in addition of a client authentication.
|
1026
|
+
|
1027
|
+
OAuth 2.0 public clients utilizing the Authorization Code Grant are susceptible to
|
1028
|
+
the authorization code interception attack. This specification describes the attack as well as
|
1029
|
+
a technique to mitigate against the threat through the use of Proof Key for Code Exchange
|
1030
|
+
(PKCE, pronounced “pixy”). See RFC7636.
|
1031
|
+
|
1032
|
+
Keyword Arguments:
|
1033
|
+
|
1034
|
+
client_id: Client identifier.
|
1035
|
+
request (oauthlib.common.Request): OAuthlib request.
|
1036
|
+
|
1037
|
+
|
1038
|
+
Method is used by:
|
1039
|
+
|
1040
|
+
Authorization Code Grant
|
1041
|
+
|
1042
|
+
|
1043
|
+
"""
|
1044
|
+
from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
|
1045
|
+
|
1046
|
+
client = (
|
1047
|
+
DBSession.query(static.OAuth2Client)
|
1048
|
+
.filter(static.OAuth2Client.client_id == client_id)
|
1049
|
+
.one_or_none()
|
1050
|
+
)
|
1051
|
+
|
1052
|
+
return client and client.pkce_required # type: ignore
|
1053
|
+
|
1054
|
+
def get_code_challenge(self, code: str, request: oauthlib.common.Request) -> Optional[str]:
|
1055
|
+
"""
|
1056
|
+
Is called for every “token” requests.
|
1057
|
+
|
1058
|
+
When the server issues the authorization code in the authorization response, it MUST associate the
|
1059
|
+
code_challenge and code_challenge_method values with the authorization code so it can be
|
1060
|
+
verified later.
|
1061
|
+
|
1062
|
+
Typically, the code_challenge and code_challenge_method values are stored in encrypted form in
|
1063
|
+
the code itself but could alternatively be stored on the server associated with the code.
|
1064
|
+
The server MUST NOT include the code_challenge value in client requests in a form that other
|
1065
|
+
entities can extract.
|
1066
|
+
|
1067
|
+
Return the code_challenge associated to the code. If None is returned, code is considered to not
|
1068
|
+
be associated to any challenges.
|
1069
|
+
|
1070
|
+
Keyword Arguments:
|
1071
|
+
|
1072
|
+
code: Authorization code.
|
1073
|
+
request: OAuthlib request.
|
1074
|
+
|
1075
|
+
Return:
|
1076
|
+
|
1077
|
+
code_challenge string
|
1078
|
+
|
1079
|
+
Method is used by:
|
1080
|
+
|
1081
|
+
Authorization Code Grant - when PKCE is active
|
1082
|
+
"""
|
1083
|
+
from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
|
1084
|
+
|
1085
|
+
LOG.debug("get_code_challenge")
|
1086
|
+
|
1087
|
+
authorization_code = (
|
1088
|
+
DBSession.query(static.OAuth2AuthorizationCode)
|
1089
|
+
.filter(static.OAuth2AuthorizationCode.code == code)
|
1090
|
+
.one_or_none()
|
1091
|
+
)
|
1092
|
+
if authorization_code:
|
1093
|
+
return authorization_code.challenge # type: ignore
|
1094
|
+
LOG.debug("get_code_challenge authorization_code not found")
|
1095
|
+
return None
|
1096
|
+
|
1097
|
+
def get_code_challenge_method(self, code: str, request: oauthlib.common.Request) -> Optional[str]:
|
1098
|
+
"""
|
1099
|
+
Is called during the “token” request processing.
|
1100
|
+
|
1101
|
+
When a code_verifier and a code_challenge has been provided.
|
1102
|
+
|
1103
|
+
See .get_code_challenge.
|
1104
|
+
|
1105
|
+
Must return plain or S256. You can return a custom value if you have implemented your own
|
1106
|
+
AuthorizationCodeGrant class.
|
1107
|
+
|
1108
|
+
Keyword Arguments:
|
1109
|
+
|
1110
|
+
code: Authorization code.
|
1111
|
+
request: OAuthlib request.
|
1112
|
+
|
1113
|
+
Return type:
|
1114
|
+
|
1115
|
+
code_challenge_method string
|
1116
|
+
|
1117
|
+
Method is used by:
|
1118
|
+
|
1119
|
+
Authorization Code Grant - when PKCE is active
|
1120
|
+
"""
|
1121
|
+
from c2cgeoportal_commons.models import DBSession, static # pylint: disable=import-outside-toplevel
|
1122
|
+
|
1123
|
+
LOG.debug("get_code_challenge_method")
|
1124
|
+
|
1125
|
+
authorization_code = (
|
1126
|
+
DBSession.query(static.OAuth2AuthorizationCode)
|
1127
|
+
.filter(static.OAuth2AuthorizationCode.code == code)
|
1128
|
+
.one_or_none()
|
1129
|
+
)
|
1130
|
+
if authorization_code:
|
1131
|
+
return authorization_code.challenge_method # type: ignore
|
1132
|
+
LOG.debug("get_code_challenge_method authorization_code not found")
|
1133
|
+
return None
|
1134
|
+
|
1018
1135
|
|
1019
1136
|
def get_oauth_client(settings: Dict[str, Any]) -> oauthlib.oauth2.WebApplicationServer:
|
1020
1137
|
"""Get the oauth2 client, with a cache."""
|