udata 9.1.2.dev30355__py2.py3-none-any.whl → 9.1.2.dev30454__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.
Potentially problematic release.
This version of udata might be problematic. Click here for more details.
- tasks/__init__.py +109 -107
- tasks/helpers.py +18 -18
- udata/__init__.py +4 -4
- udata/admin/views.py +5 -5
- udata/api/__init__.py +111 -134
- udata/api/commands.py +45 -37
- udata/api/errors.py +5 -4
- udata/api/fields.py +23 -21
- udata/api/oauth2.py +55 -74
- udata/api/parsers.py +15 -15
- udata/api/signals.py +1 -1
- udata/api_fields.py +137 -89
- udata/app.py +58 -55
- udata/assets.py +5 -5
- udata/auth/__init__.py +37 -26
- udata/auth/forms.py +23 -15
- udata/auth/helpers.py +1 -1
- udata/auth/mails.py +3 -3
- udata/auth/password_validation.py +19 -15
- udata/auth/views.py +94 -68
- udata/commands/__init__.py +71 -69
- udata/commands/cache.py +7 -7
- udata/commands/db.py +201 -140
- udata/commands/dcat.py +36 -30
- udata/commands/fixtures.py +100 -84
- udata/commands/images.py +21 -20
- udata/commands/info.py +17 -20
- udata/commands/init.py +10 -10
- udata/commands/purge.py +12 -13
- udata/commands/serve.py +41 -29
- udata/commands/static.py +16 -18
- udata/commands/test.py +20 -20
- udata/commands/tests/fixtures.py +26 -24
- udata/commands/worker.py +31 -33
- udata/core/__init__.py +12 -12
- udata/core/activity/__init__.py +0 -1
- udata/core/activity/api.py +59 -49
- udata/core/activity/models.py +28 -26
- udata/core/activity/signals.py +1 -1
- udata/core/activity/tasks.py +16 -10
- udata/core/badges/api.py +6 -6
- udata/core/badges/commands.py +14 -13
- udata/core/badges/fields.py +8 -5
- udata/core/badges/forms.py +7 -4
- udata/core/badges/models.py +16 -31
- udata/core/badges/permissions.py +1 -3
- udata/core/badges/signals.py +2 -2
- udata/core/badges/tasks.py +3 -2
- udata/core/badges/tests/test_commands.py +10 -10
- udata/core/badges/tests/test_model.py +24 -31
- udata/core/contact_point/api.py +19 -18
- udata/core/contact_point/api_fields.py +21 -14
- udata/core/contact_point/factories.py +2 -2
- udata/core/contact_point/forms.py +7 -6
- udata/core/contact_point/models.py +3 -5
- udata/core/dataservices/api.py +26 -21
- udata/core/dataservices/factories.py +13 -11
- udata/core/dataservices/models.py +35 -40
- udata/core/dataservices/permissions.py +4 -4
- udata/core/dataservices/rdf.py +40 -17
- udata/core/dataservices/tasks.py +4 -3
- udata/core/dataset/actions.py +10 -10
- udata/core/dataset/activities.py +21 -23
- udata/core/dataset/api.py +321 -298
- udata/core/dataset/api_fields.py +443 -271
- udata/core/dataset/apiv2.py +305 -229
- udata/core/dataset/commands.py +38 -36
- udata/core/dataset/constants.py +61 -54
- udata/core/dataset/csv.py +70 -74
- udata/core/dataset/events.py +39 -32
- udata/core/dataset/exceptions.py +8 -4
- udata/core/dataset/factories.py +57 -65
- udata/core/dataset/forms.py +87 -63
- udata/core/dataset/models.py +336 -280
- udata/core/dataset/permissions.py +9 -6
- udata/core/dataset/preview.py +15 -17
- udata/core/dataset/rdf.py +156 -122
- udata/core/dataset/search.py +92 -77
- udata/core/dataset/signals.py +1 -1
- udata/core/dataset/tasks.py +63 -54
- udata/core/discussions/actions.py +5 -5
- udata/core/discussions/api.py +124 -120
- udata/core/discussions/factories.py +2 -2
- udata/core/discussions/forms.py +9 -7
- udata/core/discussions/metrics.py +1 -3
- udata/core/discussions/models.py +25 -24
- udata/core/discussions/notifications.py +18 -14
- udata/core/discussions/permissions.py +3 -3
- udata/core/discussions/signals.py +4 -4
- udata/core/discussions/tasks.py +24 -28
- udata/core/followers/api.py +32 -33
- udata/core/followers/models.py +9 -9
- udata/core/followers/signals.py +3 -3
- udata/core/jobs/actions.py +7 -7
- udata/core/jobs/api.py +99 -92
- udata/core/jobs/commands.py +48 -49
- udata/core/jobs/forms.py +11 -11
- udata/core/jobs/models.py +6 -6
- udata/core/metrics/__init__.py +2 -2
- udata/core/metrics/commands.py +34 -30
- udata/core/metrics/models.py +2 -4
- udata/core/metrics/signals.py +1 -1
- udata/core/metrics/tasks.py +3 -3
- udata/core/organization/activities.py +12 -15
- udata/core/organization/api.py +167 -174
- udata/core/organization/api_fields.py +183 -124
- udata/core/organization/apiv2.py +32 -32
- udata/core/organization/commands.py +20 -22
- udata/core/organization/constants.py +11 -11
- udata/core/organization/csv.py +17 -15
- udata/core/organization/factories.py +8 -11
- udata/core/organization/forms.py +32 -26
- udata/core/organization/metrics.py +2 -1
- udata/core/organization/models.py +87 -67
- udata/core/organization/notifications.py +18 -14
- udata/core/organization/permissions.py +10 -11
- udata/core/organization/rdf.py +14 -14
- udata/core/organization/search.py +30 -28
- udata/core/organization/signals.py +7 -7
- udata/core/organization/tasks.py +42 -61
- udata/core/owned.py +38 -27
- udata/core/post/api.py +82 -81
- udata/core/post/constants.py +8 -5
- udata/core/post/factories.py +4 -4
- udata/core/post/forms.py +13 -14
- udata/core/post/models.py +20 -22
- udata/core/post/tests/test_api.py +30 -32
- udata/core/reports/api.py +8 -7
- udata/core/reports/constants.py +1 -3
- udata/core/reports/models.py +10 -10
- udata/core/reuse/activities.py +15 -19
- udata/core/reuse/api.py +123 -126
- udata/core/reuse/api_fields.py +120 -85
- udata/core/reuse/apiv2.py +11 -10
- udata/core/reuse/constants.py +23 -23
- udata/core/reuse/csv.py +18 -18
- udata/core/reuse/factories.py +5 -9
- udata/core/reuse/forms.py +24 -21
- udata/core/reuse/models.py +55 -51
- udata/core/reuse/permissions.py +2 -2
- udata/core/reuse/search.py +49 -46
- udata/core/reuse/signals.py +1 -1
- udata/core/reuse/tasks.py +4 -5
- udata/core/site/api.py +47 -50
- udata/core/site/factories.py +2 -2
- udata/core/site/forms.py +4 -5
- udata/core/site/models.py +94 -63
- udata/core/site/rdf.py +14 -14
- udata/core/spam/api.py +16 -9
- udata/core/spam/constants.py +4 -4
- udata/core/spam/fields.py +13 -7
- udata/core/spam/models.py +27 -20
- udata/core/spam/signals.py +1 -1
- udata/core/spam/tests/test_spam.py +6 -5
- udata/core/spatial/api.py +72 -80
- udata/core/spatial/api_fields.py +73 -58
- udata/core/spatial/commands.py +67 -64
- udata/core/spatial/constants.py +3 -3
- udata/core/spatial/factories.py +37 -54
- udata/core/spatial/forms.py +27 -26
- udata/core/spatial/geoids.py +17 -17
- udata/core/spatial/models.py +43 -47
- udata/core/spatial/tasks.py +2 -1
- udata/core/spatial/tests/test_api.py +115 -130
- udata/core/spatial/tests/test_fields.py +74 -77
- udata/core/spatial/tests/test_geoid.py +22 -22
- udata/core/spatial/tests/test_models.py +5 -7
- udata/core/spatial/translations.py +16 -16
- udata/core/storages/__init__.py +16 -18
- udata/core/storages/api.py +66 -64
- udata/core/storages/tasks.py +7 -7
- udata/core/storages/utils.py +15 -15
- udata/core/storages/views.py +5 -6
- udata/core/tags/api.py +17 -14
- udata/core/tags/csv.py +4 -4
- udata/core/tags/models.py +8 -5
- udata/core/tags/tasks.py +11 -13
- udata/core/tags/views.py +4 -4
- udata/core/topic/api.py +84 -73
- udata/core/topic/apiv2.py +157 -127
- udata/core/topic/factories.py +3 -4
- udata/core/topic/forms.py +12 -14
- udata/core/topic/models.py +14 -19
- udata/core/topic/parsers.py +26 -26
- udata/core/user/activities.py +30 -29
- udata/core/user/api.py +151 -152
- udata/core/user/api_fields.py +132 -100
- udata/core/user/apiv2.py +7 -7
- udata/core/user/commands.py +38 -38
- udata/core/user/factories.py +8 -9
- udata/core/user/forms.py +14 -11
- udata/core/user/metrics.py +2 -2
- udata/core/user/models.py +68 -69
- udata/core/user/permissions.py +4 -5
- udata/core/user/rdf.py +7 -8
- udata/core/user/tasks.py +2 -2
- udata/core/user/tests/test_user_model.py +24 -16
- udata/cors.py +99 -0
- udata/db/tasks.py +2 -1
- udata/entrypoints.py +35 -31
- udata/errors.py +2 -1
- udata/event/values.py +6 -6
- udata/factories.py +2 -2
- udata/features/identicon/api.py +5 -6
- udata/features/identicon/backends.py +48 -55
- udata/features/identicon/tests/test_backends.py +4 -5
- udata/features/notifications/__init__.py +0 -1
- udata/features/notifications/actions.py +9 -9
- udata/features/notifications/api.py +17 -13
- udata/features/territories/__init__.py +12 -10
- udata/features/territories/api.py +14 -15
- udata/features/territories/models.py +23 -28
- udata/features/transfer/actions.py +8 -11
- udata/features/transfer/api.py +84 -77
- udata/features/transfer/factories.py +2 -1
- udata/features/transfer/models.py +11 -12
- udata/features/transfer/notifications.py +19 -15
- udata/features/transfer/permissions.py +5 -5
- udata/forms/__init__.py +5 -2
- udata/forms/fields.py +164 -172
- udata/forms/validators.py +19 -22
- udata/forms/widgets.py +9 -13
- udata/frontend/__init__.py +31 -26
- udata/frontend/csv.py +68 -58
- udata/frontend/markdown.py +40 -44
- udata/harvest/actions.py +89 -77
- udata/harvest/api.py +294 -238
- udata/harvest/backends/__init__.py +4 -4
- udata/harvest/backends/base.py +128 -111
- udata/harvest/backends/dcat.py +80 -66
- udata/harvest/commands.py +56 -60
- udata/harvest/csv.py +8 -8
- udata/harvest/exceptions.py +6 -3
- udata/harvest/filters.py +24 -23
- udata/harvest/forms.py +27 -28
- udata/harvest/models.py +88 -80
- udata/harvest/notifications.py +15 -10
- udata/harvest/signals.py +13 -13
- udata/harvest/tasks.py +11 -10
- udata/harvest/tests/factories.py +23 -24
- udata/harvest/tests/test_actions.py +136 -166
- udata/harvest/tests/test_api.py +220 -214
- udata/harvest/tests/test_base_backend.py +117 -112
- udata/harvest/tests/test_dcat_backend.py +380 -308
- udata/harvest/tests/test_filters.py +33 -22
- udata/harvest/tests/test_models.py +11 -14
- udata/harvest/tests/test_notifications.py +6 -7
- udata/harvest/tests/test_tasks.py +7 -6
- udata/i18n.py +237 -78
- udata/linkchecker/backends.py +5 -11
- udata/linkchecker/checker.py +23 -22
- udata/linkchecker/commands.py +4 -6
- udata/linkchecker/models.py +6 -6
- udata/linkchecker/tasks.py +18 -20
- udata/mail.py +21 -21
- udata/migrations/2020-07-24-remove-s-from-scope-oauth.py +9 -8
- udata/migrations/2020-08-24-add-fs-filename.py +9 -8
- udata/migrations/2020-09-28-update-reuses-datasets-metrics.py +5 -4
- udata/migrations/2020-10-16-migrate-ods-resources.py +9 -10
- udata/migrations/2021-04-08-update-schema-with-new-structure.py +8 -7
- udata/migrations/2021-05-27-fix-default-schema-name.py +7 -6
- udata/migrations/2021-07-05-remove-unused-badges.py +17 -15
- udata/migrations/2021-07-07-update-schema-for-community-resources.py +7 -6
- udata/migrations/2021-08-17-follow-integrity.py +5 -4
- udata/migrations/2021-08-17-harvest-integrity.py +13 -12
- udata/migrations/2021-08-17-oauth2client-integrity.py +5 -4
- udata/migrations/2021-08-17-transfer-integrity.py +5 -4
- udata/migrations/2021-08-17-users-integrity.py +9 -8
- udata/migrations/2021-12-14-reuse-topics.py +7 -6
- udata/migrations/2022-04-21-improve-extension-detection.py +8 -7
- udata/migrations/2022-09-22-clean-inactive-harvest-datasets.py +16 -14
- udata/migrations/2022-10-10-add-fs_uniquifier-to-user-model.py +6 -6
- udata/migrations/2022-10-10-migrate-harvest-extras.py +36 -26
- udata/migrations/2023-02-08-rename-internal-dates.py +46 -28
- udata/migrations/2024-01-29-fix-reuse-and-dataset-with-private-None.py +10 -8
- udata/migrations/2024-03-22-migrate-activity-kwargs-to-extras.py +6 -4
- udata/migrations/2024-06-11-fix-reuse-datasets-references.py +7 -6
- udata/migrations/__init__.py +123 -105
- udata/models/__init__.py +4 -4
- udata/mongo/__init__.py +13 -11
- udata/mongo/badges_field.py +3 -2
- udata/mongo/datetime_fields.py +13 -12
- udata/mongo/document.py +17 -16
- udata/mongo/engine.py +15 -16
- udata/mongo/errors.py +2 -1
- udata/mongo/extras_fields.py +30 -20
- udata/mongo/queryset.py +12 -12
- udata/mongo/slug_fields.py +38 -28
- udata/mongo/taglist_field.py +1 -2
- udata/mongo/url_field.py +5 -5
- udata/mongo/uuid_fields.py +4 -3
- udata/notifications/__init__.py +1 -1
- udata/notifications/mattermost.py +10 -9
- udata/rdf.py +167 -188
- udata/routing.py +40 -45
- udata/search/__init__.py +18 -19
- udata/search/adapter.py +17 -16
- udata/search/commands.py +44 -51
- udata/search/fields.py +13 -20
- udata/search/query.py +23 -18
- udata/search/result.py +9 -10
- udata/sentry.py +21 -19
- udata/settings.py +262 -198
- udata/sitemap.py +8 -6
- udata/storage/s3.py +20 -13
- udata/tags.py +4 -5
- udata/tasks.py +43 -42
- udata/tests/__init__.py +9 -6
- udata/tests/api/__init__.py +8 -6
- udata/tests/api/test_auth_api.py +395 -321
- udata/tests/api/test_base_api.py +33 -35
- udata/tests/api/test_contact_points.py +7 -9
- udata/tests/api/test_dataservices_api.py +211 -158
- udata/tests/api/test_datasets_api.py +823 -812
- udata/tests/api/test_follow_api.py +13 -15
- udata/tests/api/test_me_api.py +95 -112
- udata/tests/api/test_organizations_api.py +301 -339
- udata/tests/api/test_reports_api.py +35 -25
- udata/tests/api/test_reuses_api.py +134 -139
- udata/tests/api/test_swagger.py +5 -5
- udata/tests/api/test_tags_api.py +18 -25
- udata/tests/api/test_topics_api.py +94 -94
- udata/tests/api/test_transfer_api.py +53 -48
- udata/tests/api/test_user_api.py +128 -141
- udata/tests/apiv2/test_datasets.py +290 -198
- udata/tests/apiv2/test_me_api.py +10 -11
- udata/tests/apiv2/test_organizations.py +56 -74
- udata/tests/apiv2/test_swagger.py +5 -5
- udata/tests/apiv2/test_topics.py +69 -87
- udata/tests/cli/test_cli_base.py +8 -8
- udata/tests/cli/test_db_cli.py +21 -19
- udata/tests/dataservice/test_dataservice_tasks.py +8 -12
- udata/tests/dataset/test_csv_adapter.py +44 -35
- udata/tests/dataset/test_dataset_actions.py +2 -3
- udata/tests/dataset/test_dataset_commands.py +7 -8
- udata/tests/dataset/test_dataset_events.py +36 -29
- udata/tests/dataset/test_dataset_model.py +224 -217
- udata/tests/dataset/test_dataset_rdf.py +142 -131
- udata/tests/dataset/test_dataset_tasks.py +15 -15
- udata/tests/dataset/test_resource_preview.py +10 -13
- udata/tests/features/territories/__init__.py +9 -13
- udata/tests/features/territories/test_territories_api.py +71 -91
- udata/tests/forms/test_basic_fields.py +7 -7
- udata/tests/forms/test_current_user_field.py +39 -66
- udata/tests/forms/test_daterange_field.py +31 -39
- udata/tests/forms/test_dict_field.py +28 -26
- udata/tests/forms/test_extras_fields.py +102 -76
- udata/tests/forms/test_form_field.py +8 -8
- udata/tests/forms/test_image_field.py +33 -26
- udata/tests/forms/test_model_field.py +134 -123
- udata/tests/forms/test_model_list_field.py +7 -7
- udata/tests/forms/test_nested_model_list_field.py +117 -79
- udata/tests/forms/test_publish_as_field.py +36 -65
- udata/tests/forms/test_reference_field.py +34 -53
- udata/tests/forms/test_user_forms.py +23 -21
- udata/tests/forms/test_uuid_field.py +6 -10
- udata/tests/frontend/__init__.py +9 -6
- udata/tests/frontend/test_auth.py +7 -6
- udata/tests/frontend/test_csv.py +81 -96
- udata/tests/frontend/test_hooks.py +43 -43
- udata/tests/frontend/test_markdown.py +211 -191
- udata/tests/helpers.py +32 -37
- udata/tests/models.py +2 -2
- udata/tests/organization/test_csv_adapter.py +21 -16
- udata/tests/organization/test_notifications.py +11 -18
- udata/tests/organization/test_organization_model.py +13 -13
- udata/tests/organization/test_organization_rdf.py +29 -22
- udata/tests/organization/test_organization_tasks.py +16 -17
- udata/tests/plugin.py +79 -73
- udata/tests/reuse/test_reuse_model.py +21 -21
- udata/tests/reuse/test_reuse_task.py +11 -13
- udata/tests/search/__init__.py +11 -12
- udata/tests/search/test_adapter.py +60 -70
- udata/tests/search/test_query.py +16 -16
- udata/tests/search/test_results.py +10 -7
- udata/tests/site/test_site_api.py +11 -16
- udata/tests/site/test_site_metrics.py +20 -30
- udata/tests/site/test_site_model.py +4 -5
- udata/tests/site/test_site_rdf.py +94 -78
- udata/tests/test_activity.py +17 -17
- udata/tests/test_cors.py +62 -0
- udata/tests/test_discussions.py +292 -299
- udata/tests/test_i18n.py +37 -40
- udata/tests/test_linkchecker.py +91 -85
- udata/tests/test_mail.py +13 -17
- udata/tests/test_migrations.py +219 -180
- udata/tests/test_model.py +164 -157
- udata/tests/test_notifications.py +17 -17
- udata/tests/test_owned.py +14 -14
- udata/tests/test_rdf.py +25 -23
- udata/tests/test_routing.py +89 -93
- udata/tests/test_storages.py +137 -128
- udata/tests/test_tags.py +44 -46
- udata/tests/test_topics.py +7 -7
- udata/tests/test_transfer.py +42 -49
- udata/tests/test_uris.py +160 -161
- udata/tests/test_utils.py +79 -71
- udata/tests/user/test_user_rdf.py +5 -9
- udata/tests/workers/test_jobs_commands.py +57 -58
- udata/tests/workers/test_tasks_routing.py +23 -29
- udata/tests/workers/test_workers_api.py +125 -131
- udata/tests/workers/test_workers_helpers.py +6 -6
- udata/tracking.py +4 -6
- udata/uris.py +45 -46
- udata/utils.py +68 -66
- udata/wsgi.py +1 -1
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/METADATA +7 -3
- udata-9.1.2.dev30454.dist-info/RECORD +706 -0
- udata-9.1.2.dev30355.dist-info/RECORD +0 -704
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/LICENSE +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/WHEEL +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/entry_points.txt +0 -0
- {udata-9.1.2.dev30355.dist-info → udata-9.1.2.dev30454.dist-info}/top_level.txt +0 -0
udata/tests/api/test_auth_api.py
CHANGED
|
@@ -1,338 +1,365 @@
|
|
|
1
|
-
import pytest
|
|
2
|
-
|
|
3
1
|
from base64 import b64encode
|
|
4
2
|
from urllib.parse import parse_qs
|
|
5
3
|
|
|
6
|
-
|
|
4
|
+
import pytest
|
|
7
5
|
from authlib.common.security import generate_token
|
|
8
|
-
from authlib.common.urls import
|
|
6
|
+
from authlib.common.urls import url_decode, urlparse
|
|
9
7
|
from authlib.oauth2.rfc7636 import (
|
|
10
8
|
create_s256_code_challenge,
|
|
11
9
|
)
|
|
10
|
+
from flask import url_for
|
|
12
11
|
|
|
13
|
-
from udata.api import
|
|
12
|
+
from udata.api import API, api
|
|
14
13
|
from udata.api.oauth2 import OAuth2Client, OAuth2Token
|
|
15
14
|
from udata.auth import PermissionDenied
|
|
16
15
|
from udata.core.user.factories import UserFactory
|
|
17
16
|
from udata.forms import Form, fields, validators
|
|
18
17
|
from udata.tests.helpers import (
|
|
19
|
-
assert200,
|
|
18
|
+
assert200,
|
|
19
|
+
assert400,
|
|
20
|
+
assert401,
|
|
21
|
+
assert403,
|
|
22
|
+
assert_status,
|
|
20
23
|
)
|
|
21
24
|
|
|
22
|
-
ns = api.namespace(
|
|
25
|
+
ns = api.namespace("fake", "A Fake namespace")
|
|
23
26
|
|
|
24
27
|
|
|
25
28
|
class FakeForm(Form):
|
|
26
29
|
required = fields.StringField(validators=[validators.DataRequired()])
|
|
27
|
-
choices = fields.SelectField(choices=((
|
|
30
|
+
choices = fields.SelectField(choices=(("first", ""), ("second", "")))
|
|
28
31
|
email = fields.StringField(validators=[validators.Email()])
|
|
29
32
|
|
|
30
33
|
|
|
31
|
-
@ns.route(
|
|
34
|
+
@ns.route("/", endpoint="fake")
|
|
32
35
|
class FakeAPI(API):
|
|
33
36
|
@api.secure
|
|
34
37
|
def post(self):
|
|
35
|
-
return {
|
|
38
|
+
return {"success": True}
|
|
36
39
|
|
|
37
40
|
def get(self):
|
|
38
|
-
return {
|
|
41
|
+
return {"success": True}
|
|
39
42
|
|
|
40
43
|
def put(self):
|
|
41
44
|
api.validate(FakeForm)
|
|
42
|
-
return {
|
|
45
|
+
return {"success": True}
|
|
43
46
|
|
|
44
47
|
|
|
45
48
|
def basic_header(client):
|
|
46
|
-
payload =
|
|
47
|
-
token = b64encode(payload.encode(
|
|
48
|
-
return {
|
|
49
|
+
payload = ":".join((client.client_id, client.secret))
|
|
50
|
+
token = b64encode(payload.encode("utf-8")).decode("utf8")
|
|
51
|
+
return {"Authorization": "Basic {}".format(token)}
|
|
49
52
|
|
|
50
53
|
|
|
51
54
|
@pytest.fixture
|
|
52
55
|
def oauth(app, request):
|
|
53
|
-
marker = request.node.get_closest_marker(
|
|
56
|
+
marker = request.node.get_closest_marker("oauth")
|
|
54
57
|
custom_kwargs = marker.kwargs if marker else {}
|
|
55
58
|
kwargs = dict(
|
|
56
|
-
name=
|
|
59
|
+
name="test-client",
|
|
57
60
|
owner=UserFactory(),
|
|
58
|
-
redirect_uris=[
|
|
61
|
+
redirect_uris=["https://test.org/callback"],
|
|
59
62
|
)
|
|
60
63
|
kwargs.update(custom_kwargs)
|
|
61
64
|
return OAuth2Client.objects.create(**kwargs)
|
|
62
65
|
|
|
63
66
|
|
|
64
|
-
@pytest.mark.usefixtures(
|
|
67
|
+
@pytest.mark.usefixtures("clean_db")
|
|
65
68
|
class APIAuthTest:
|
|
66
69
|
modules = []
|
|
67
70
|
|
|
68
71
|
def test_no_auth(self, api):
|
|
69
|
-
|
|
70
|
-
response = api.get(url_for(
|
|
72
|
+
"""Should not return a content type if there is no content on delete"""
|
|
73
|
+
response = api.get(url_for("api.fake"))
|
|
71
74
|
|
|
72
75
|
assert200(response)
|
|
73
|
-
assert response.content_type ==
|
|
74
|
-
assert response.json == {
|
|
76
|
+
assert response.content_type == "application/json"
|
|
77
|
+
assert response.json == {"success": True}
|
|
75
78
|
|
|
76
79
|
def test_session_auth(self, api):
|
|
77
|
-
|
|
80
|
+
"""Should handle session authentication"""
|
|
78
81
|
api.client.login() # Session auth
|
|
79
82
|
|
|
80
|
-
response = api.post(url_for(
|
|
83
|
+
response = api.post(url_for("api.fake"))
|
|
81
84
|
|
|
82
85
|
assert200(response)
|
|
83
|
-
assert response.content_type ==
|
|
84
|
-
assert response.json == {
|
|
86
|
+
assert response.content_type == "application/json"
|
|
87
|
+
assert response.json == {"success": True}
|
|
85
88
|
|
|
86
89
|
def test_header_auth(self, api):
|
|
87
|
-
|
|
90
|
+
"""Should handle header API Key authentication"""
|
|
88
91
|
with api.user() as user: # API Key auth
|
|
89
|
-
response = api.post(url_for(
|
|
90
|
-
headers={'X-API-KEY': user.apikey})
|
|
92
|
+
response = api.post(url_for("api.fake"), headers={"X-API-KEY": user.apikey})
|
|
91
93
|
|
|
92
94
|
assert200(response)
|
|
93
|
-
assert response.content_type ==
|
|
94
|
-
assert response.json == {
|
|
95
|
+
assert response.content_type == "application/json"
|
|
96
|
+
assert response.json == {"success": True}
|
|
95
97
|
|
|
96
98
|
def test_oauth_auth(self, api, oauth):
|
|
97
|
-
|
|
99
|
+
"""Should handle OAuth header authentication"""
|
|
98
100
|
user = UserFactory()
|
|
99
101
|
token = OAuth2Token.objects.create(
|
|
100
102
|
client=oauth,
|
|
101
103
|
user=user,
|
|
102
|
-
access_token=
|
|
103
|
-
refresh_token=
|
|
104
|
+
access_token="access-token",
|
|
105
|
+
refresh_token="refresh-token",
|
|
104
106
|
)
|
|
105
107
|
|
|
106
|
-
response = api.post(
|
|
107
|
-
|
|
108
|
-
|
|
108
|
+
response = api.post(
|
|
109
|
+
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token.access_token])}
|
|
110
|
+
)
|
|
109
111
|
|
|
110
112
|
assert200(response)
|
|
111
|
-
assert response.content_type ==
|
|
112
|
-
assert response.json == {
|
|
113
|
+
assert response.content_type == "application/json"
|
|
114
|
+
assert response.json == {"success": True}
|
|
113
115
|
|
|
114
116
|
def test_bad_oauth_auth(self, api, oauth):
|
|
115
|
-
|
|
117
|
+
"""Should handle wrong OAuth header authentication"""
|
|
116
118
|
user = UserFactory()
|
|
117
119
|
OAuth2Token.objects.create(
|
|
118
120
|
client=oauth,
|
|
119
121
|
user=user,
|
|
120
|
-
access_token=
|
|
121
|
-
refresh_token=
|
|
122
|
+
access_token="access-token",
|
|
123
|
+
refresh_token="refresh-token",
|
|
122
124
|
)
|
|
123
125
|
|
|
124
|
-
response = api.post(
|
|
125
|
-
|
|
126
|
-
|
|
126
|
+
response = api.post(
|
|
127
|
+
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", "not-my-token"])}
|
|
128
|
+
)
|
|
127
129
|
|
|
128
130
|
assert401(response)
|
|
129
|
-
assert response.content_type ==
|
|
131
|
+
assert response.content_type == "application/json"
|
|
130
132
|
|
|
131
133
|
def test_no_apikey(self, api):
|
|
132
|
-
|
|
133
|
-
response = api.post(url_for(
|
|
134
|
+
"""Should raise a HTTP 401 if no API Key is provided"""
|
|
135
|
+
response = api.post(url_for("api.fake"))
|
|
134
136
|
|
|
135
137
|
assert401(response)
|
|
136
|
-
assert response.content_type ==
|
|
137
|
-
assert
|
|
138
|
+
assert response.content_type == "application/json"
|
|
139
|
+
assert "message" in response.json
|
|
138
140
|
|
|
139
141
|
def test_invalid_apikey(self, api):
|
|
140
|
-
|
|
141
|
-
response = api.post(url_for(
|
|
142
|
+
"""Should raise a HTTP 401 if an invalid API Key is provided"""
|
|
143
|
+
response = api.post(url_for("api.fake"), headers={"X-API-KEY": "fake"})
|
|
142
144
|
|
|
143
145
|
assert401(response)
|
|
144
|
-
assert response.content_type ==
|
|
145
|
-
assert
|
|
146
|
+
assert response.content_type == "application/json"
|
|
147
|
+
assert "message" in response.json
|
|
146
148
|
|
|
147
149
|
def test_inactive_user(self, api):
|
|
148
|
-
|
|
150
|
+
"""Should raise a HTTP 401 if the user is inactive"""
|
|
149
151
|
user = UserFactory(active=False)
|
|
150
152
|
with api.user(user) as user:
|
|
151
|
-
response = api.post(url_for(
|
|
152
|
-
headers={'X-API-KEY': user.apikey})
|
|
153
|
+
response = api.post(url_for("api.fake"), headers={"X-API-KEY": user.apikey})
|
|
153
154
|
|
|
154
155
|
assert401(response)
|
|
155
|
-
assert response.content_type ==
|
|
156
|
-
assert
|
|
156
|
+
assert response.content_type == "application/json"
|
|
157
|
+
assert "message" in response.json
|
|
157
158
|
|
|
158
159
|
def test_deleted_user(self, api):
|
|
159
|
-
|
|
160
|
+
"""Should raise a HTTP 401 if the user is deleted"""
|
|
160
161
|
user = UserFactory()
|
|
161
162
|
user.mark_as_deleted()
|
|
162
163
|
with api.user(user) as user:
|
|
163
|
-
response = api.post(url_for(
|
|
164
|
-
headers={'X-API-KEY': user.apikey})
|
|
164
|
+
response = api.post(url_for("api.fake"), headers={"X-API-KEY": user.apikey})
|
|
165
165
|
|
|
166
166
|
assert401(response)
|
|
167
|
-
assert response.content_type ==
|
|
168
|
-
assert
|
|
167
|
+
assert response.content_type == "application/json"
|
|
168
|
+
assert "message" in response.json
|
|
169
169
|
|
|
170
170
|
def test_validation_errors(self, api):
|
|
171
|
-
|
|
172
|
-
response = api.put(url_for(
|
|
171
|
+
"""Should raise a HTTP 400 and returns errors on validation error"""
|
|
172
|
+
response = api.put(url_for("api.fake"), {"email": "wrong"})
|
|
173
173
|
|
|
174
174
|
assert400(response)
|
|
175
|
-
assert response.content_type ==
|
|
175
|
+
assert response.content_type == "application/json"
|
|
176
176
|
|
|
177
|
-
for field in
|
|
178
|
-
assert field in response.json[
|
|
179
|
-
assert isinstance(response.json[
|
|
177
|
+
for field in "required", "email", "choices":
|
|
178
|
+
assert field in response.json["errors"]
|
|
179
|
+
assert isinstance(response.json["errors"][field], list)
|
|
180
180
|
|
|
181
181
|
def test_no_validation_error(self, api):
|
|
182
|
-
|
|
183
|
-
response = api.put(
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
182
|
+
"""Should pass if no validation error"""
|
|
183
|
+
response = api.put(
|
|
184
|
+
url_for("api.fake"),
|
|
185
|
+
{
|
|
186
|
+
"required": "value",
|
|
187
|
+
"email": "coucou@cmoi.fr",
|
|
188
|
+
"choices": "first",
|
|
189
|
+
},
|
|
190
|
+
)
|
|
188
191
|
|
|
189
192
|
assert200(response)
|
|
190
|
-
assert response.json == {
|
|
193
|
+
assert response.json == {"success": True}
|
|
191
194
|
|
|
192
195
|
def test_authorization_display(self, client, oauth):
|
|
193
|
-
|
|
196
|
+
"""Should display the OAuth authorization page"""
|
|
194
197
|
client.login()
|
|
195
198
|
|
|
196
|
-
response = client.get(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
response = client.get(
|
|
200
|
+
url_for(
|
|
201
|
+
"oauth.authorize",
|
|
202
|
+
response_type="code",
|
|
203
|
+
client_id=oauth.client_id,
|
|
204
|
+
redirect_uri=oauth.default_redirect_uri,
|
|
205
|
+
)
|
|
206
|
+
)
|
|
202
207
|
|
|
203
208
|
assert200(response)
|
|
204
209
|
|
|
205
210
|
def test_authorization_decline(self, client, oauth):
|
|
206
|
-
|
|
211
|
+
"""Should redirect to the redirect_uri on authorization denied"""
|
|
207
212
|
client.login()
|
|
208
213
|
|
|
209
|
-
response = client.post(
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
214
|
+
response = client.post(
|
|
215
|
+
url_for(
|
|
216
|
+
"oauth.authorize",
|
|
217
|
+
response_type="code",
|
|
218
|
+
client_id=oauth.client_id,
|
|
219
|
+
redirect_uri=oauth.default_redirect_uri,
|
|
220
|
+
),
|
|
221
|
+
{
|
|
222
|
+
"scope": "default",
|
|
223
|
+
"refuse": "",
|
|
224
|
+
},
|
|
225
|
+
)
|
|
218
226
|
|
|
219
227
|
assert_status(response, 302)
|
|
220
|
-
uri, params = response.location.split(
|
|
228
|
+
uri, params = response.location.split("?")
|
|
221
229
|
assert uri == oauth.default_redirect_uri
|
|
222
230
|
|
|
223
231
|
def test_authorization_accept(self, client, oauth):
|
|
224
|
-
|
|
232
|
+
"""Should redirect to the redirect_uri on authorization accepted"""
|
|
225
233
|
client.login()
|
|
226
234
|
|
|
227
|
-
response = client.post(
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
235
|
+
response = client.post(
|
|
236
|
+
url_for(
|
|
237
|
+
"oauth.authorize",
|
|
238
|
+
response_type="code",
|
|
239
|
+
client_id=oauth.client_id,
|
|
240
|
+
redirect_uri=oauth.default_redirect_uri,
|
|
241
|
+
),
|
|
242
|
+
{
|
|
243
|
+
"scope": "default",
|
|
244
|
+
"accept": "",
|
|
245
|
+
},
|
|
246
|
+
)
|
|
236
247
|
|
|
237
248
|
assert_status(response, 302)
|
|
238
|
-
uri, params = response.location.split(
|
|
249
|
+
uri, params = response.location.split("?")
|
|
239
250
|
|
|
240
251
|
assert uri == oauth.default_redirect_uri
|
|
241
252
|
|
|
242
253
|
@pytest.mark.options(OAUTH2_ALLOW_WILDCARD_IN_REDIRECT_URI=True)
|
|
243
|
-
@pytest.mark.oauth(redirect_uris=[
|
|
254
|
+
@pytest.mark.oauth(redirect_uris=["https://*.test.org/callback"])
|
|
244
255
|
def test_authorization_accept_wildcard(self, client, oauth):
|
|
245
|
-
|
|
246
|
-
with wildcard enabled and used in config
|
|
256
|
+
"""Should redirect to the redirect_uri on authorization accepted
|
|
257
|
+
with wildcard enabled and used in config"""
|
|
247
258
|
client.login()
|
|
248
259
|
|
|
249
|
-
redirect_uri =
|
|
260
|
+
redirect_uri = "https://subdomain.test.org/callback"
|
|
250
261
|
|
|
251
|
-
response = client.post(
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
262
|
+
response = client.post(
|
|
263
|
+
url_for(
|
|
264
|
+
"oauth.authorize",
|
|
265
|
+
response_type="code",
|
|
266
|
+
client_id=oauth.client_id,
|
|
267
|
+
redirect_uri=redirect_uri,
|
|
268
|
+
),
|
|
269
|
+
{
|
|
270
|
+
"scope": "default",
|
|
271
|
+
"accept": "",
|
|
272
|
+
},
|
|
273
|
+
)
|
|
260
274
|
|
|
261
275
|
assert_status(response, 302)
|
|
262
|
-
uri, _ = response.location.split(
|
|
276
|
+
uri, _ = response.location.split("?")
|
|
263
277
|
|
|
264
278
|
assert uri == redirect_uri
|
|
265
279
|
|
|
266
280
|
@pytest.mark.options(OAUTH2_ALLOW_WILDCARD_IN_REDIRECT_URI=False)
|
|
267
|
-
@pytest.mark.oauth(redirect_uris=[
|
|
281
|
+
@pytest.mark.oauth(redirect_uris=["https://*.test.org/callback"])
|
|
268
282
|
def test_authorization_accept_no_wildcard(self, client, oauth):
|
|
269
|
-
|
|
270
|
-
without wildcard enabled while used in config
|
|
283
|
+
"""Should not redirect to the redirect_uri on authorization accepted
|
|
284
|
+
without wildcard enabled while used in config"""
|
|
271
285
|
client.login()
|
|
272
286
|
|
|
273
|
-
redirect_uri =
|
|
287
|
+
redirect_uri = "https://subdomain.test.org/callback"
|
|
274
288
|
|
|
275
|
-
response = client.post(
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
289
|
+
response = client.post(
|
|
290
|
+
url_for(
|
|
291
|
+
"oauth.authorize",
|
|
292
|
+
response_type="code",
|
|
293
|
+
client_id=oauth.client_id,
|
|
294
|
+
redirect_uri=redirect_uri,
|
|
295
|
+
),
|
|
296
|
+
{
|
|
297
|
+
"scope": "default",
|
|
298
|
+
"accept": "",
|
|
299
|
+
},
|
|
300
|
+
)
|
|
284
301
|
|
|
285
302
|
assert_status(response, 400)
|
|
286
|
-
assert
|
|
287
|
-
assert
|
|
303
|
+
assert "error" in response.json
|
|
304
|
+
assert "redirect_uri" in response.json["error_description"]
|
|
288
305
|
|
|
289
306
|
@pytest.mark.options(OAUTH2_ALLOW_WILDCARD_IN_REDIRECT_URI=True)
|
|
290
|
-
@pytest.mark.oauth(redirect_uris=[
|
|
307
|
+
@pytest.mark.oauth(redirect_uris=["https://*.test.org/callback"])
|
|
291
308
|
def test_authorization_accept_wrong_wildcard(self, client, oauth):
|
|
292
|
-
|
|
293
|
-
with wildcard enabled but mismatched from config
|
|
309
|
+
"""Should not redirect to the redirect_uri on authorization accepted
|
|
310
|
+
with wildcard enabled but mismatched from config"""
|
|
294
311
|
client.login()
|
|
295
312
|
|
|
296
|
-
redirect_uri =
|
|
313
|
+
redirect_uri = "https://subdomain.example.com/callback"
|
|
297
314
|
|
|
298
|
-
response = client.post(
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
315
|
+
response = client.post(
|
|
316
|
+
url_for(
|
|
317
|
+
"oauth.authorize",
|
|
318
|
+
response_type="code",
|
|
319
|
+
client_id=oauth.client_id,
|
|
320
|
+
redirect_uri=redirect_uri,
|
|
321
|
+
),
|
|
322
|
+
{
|
|
323
|
+
"scope": "default",
|
|
324
|
+
"accept": "",
|
|
325
|
+
},
|
|
326
|
+
)
|
|
307
327
|
|
|
308
328
|
assert_status(response, 400)
|
|
309
|
-
assert
|
|
310
|
-
assert
|
|
329
|
+
assert "error" in response.json
|
|
330
|
+
assert "redirect_uri" in response.json["error_description"]
|
|
311
331
|
|
|
312
332
|
def test_authorization_grant_token(self, client, oauth):
|
|
313
333
|
client.login()
|
|
314
334
|
|
|
315
|
-
response = client.post(
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
335
|
+
response = client.post(
|
|
336
|
+
url_for(
|
|
337
|
+
"oauth.authorize",
|
|
338
|
+
response_type="code",
|
|
339
|
+
client_id=oauth.client_id,
|
|
340
|
+
),
|
|
341
|
+
{
|
|
342
|
+
"scope": "default",
|
|
343
|
+
"accept": "",
|
|
344
|
+
},
|
|
345
|
+
)
|
|
323
346
|
|
|
324
|
-
uri, params = response.location.split(
|
|
325
|
-
code = parse_qs(params)[
|
|
347
|
+
uri, params = response.location.split("?")
|
|
348
|
+
code = parse_qs(params)["code"][0]
|
|
326
349
|
|
|
327
350
|
client.logout()
|
|
328
|
-
response = client.post(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
351
|
+
response = client.post(
|
|
352
|
+
url_for("oauth.token"),
|
|
353
|
+
{
|
|
354
|
+
"grant_type": "authorization_code",
|
|
355
|
+
"code": code,
|
|
356
|
+
},
|
|
357
|
+
headers=basic_header(oauth),
|
|
358
|
+
)
|
|
332
359
|
|
|
333
360
|
assert200(response)
|
|
334
|
-
assert response.content_type ==
|
|
335
|
-
assert
|
|
361
|
+
assert response.content_type == "application/json"
|
|
362
|
+
assert "access_token" in response.json
|
|
336
363
|
|
|
337
364
|
def test_s256_code_challenge_success_client_secret_basic(self, client, oauth):
|
|
338
365
|
code_verifier = generate_token(48)
|
|
@@ -340,40 +367,43 @@ class APIAuthTest:
|
|
|
340
367
|
|
|
341
368
|
client.login()
|
|
342
369
|
|
|
343
|
-
response = client.post(
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
370
|
+
response = client.post(
|
|
371
|
+
url_for(
|
|
372
|
+
"oauth.authorize",
|
|
373
|
+
response_type="code",
|
|
374
|
+
client_id=oauth.client_id,
|
|
375
|
+
code_challenge=code_challenge,
|
|
376
|
+
code_challenge_method="S256",
|
|
377
|
+
),
|
|
378
|
+
{
|
|
379
|
+
"scope": "default",
|
|
380
|
+
"accept": "",
|
|
381
|
+
},
|
|
382
|
+
)
|
|
383
|
+
assert "code=" in response.location
|
|
354
384
|
|
|
355
385
|
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
356
|
-
code = params[
|
|
386
|
+
code = params["code"]
|
|
357
387
|
|
|
358
|
-
response = client.post(
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
388
|
+
response = client.post(
|
|
389
|
+
url_for("oauth.token"),
|
|
390
|
+
{"grant_type": "authorization_code", "code": code, "code_verifier": code_verifier},
|
|
391
|
+
headers=basic_header(oauth),
|
|
392
|
+
)
|
|
363
393
|
|
|
364
394
|
assert200(response)
|
|
365
|
-
assert response.content_type ==
|
|
366
|
-
assert
|
|
395
|
+
assert response.content_type == "application/json"
|
|
396
|
+
assert "access_token" in response.json
|
|
367
397
|
|
|
368
|
-
token = response.json[
|
|
398
|
+
token = response.json["access_token"]
|
|
369
399
|
|
|
370
|
-
response = client.post(
|
|
371
|
-
|
|
372
|
-
|
|
400
|
+
response = client.post(
|
|
401
|
+
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token])}
|
|
402
|
+
)
|
|
373
403
|
|
|
374
404
|
assert200(response)
|
|
375
|
-
assert response.content_type ==
|
|
376
|
-
assert response.json == {
|
|
405
|
+
assert response.content_type == "application/json"
|
|
406
|
+
assert response.json == {"success": True}
|
|
377
407
|
|
|
378
408
|
def test_s256_code_challenge_success_client_secret_post(self, client, oauth):
|
|
379
409
|
code_verifier = generate_token(48)
|
|
@@ -381,147 +411,178 @@ class APIAuthTest:
|
|
|
381
411
|
|
|
382
412
|
client.login()
|
|
383
413
|
|
|
384
|
-
response = client.post(
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
414
|
+
response = client.post(
|
|
415
|
+
url_for(
|
|
416
|
+
"oauth.authorize",
|
|
417
|
+
response_type="code",
|
|
418
|
+
client_id=oauth.client_id,
|
|
419
|
+
code_challenge=code_challenge,
|
|
420
|
+
code_challenge_method="S256",
|
|
421
|
+
),
|
|
422
|
+
{
|
|
423
|
+
"scope": "default",
|
|
424
|
+
"accept": "",
|
|
425
|
+
},
|
|
426
|
+
)
|
|
427
|
+
assert "code=" in response.location
|
|
395
428
|
|
|
396
429
|
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
397
|
-
code = params[
|
|
398
|
-
|
|
399
|
-
response = client.post(
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
430
|
+
code = params["code"]
|
|
431
|
+
|
|
432
|
+
response = client.post(
|
|
433
|
+
url_for("oauth.token"),
|
|
434
|
+
{
|
|
435
|
+
"grant_type": "authorization_code",
|
|
436
|
+
"code": code,
|
|
437
|
+
"code_verifier": code_verifier,
|
|
438
|
+
"client_id": oauth.client_id,
|
|
439
|
+
"client_secret": oauth.secret,
|
|
440
|
+
},
|
|
441
|
+
)
|
|
406
442
|
|
|
407
443
|
assert200(response)
|
|
408
|
-
assert response.content_type ==
|
|
409
|
-
assert
|
|
444
|
+
assert response.content_type == "application/json"
|
|
445
|
+
assert "access_token" in response.json
|
|
410
446
|
|
|
411
|
-
token = response.json[
|
|
447
|
+
token = response.json["access_token"]
|
|
412
448
|
|
|
413
|
-
response = client.post(
|
|
414
|
-
|
|
415
|
-
|
|
449
|
+
response = client.post(
|
|
450
|
+
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token])}
|
|
451
|
+
)
|
|
416
452
|
|
|
417
453
|
assert200(response)
|
|
418
|
-
assert response.content_type ==
|
|
419
|
-
assert response.json == {
|
|
454
|
+
assert response.content_type == "application/json"
|
|
455
|
+
assert response.json == {"success": True}
|
|
420
456
|
|
|
421
457
|
def test_authorization_multiple_grant_token(self, client, oauth):
|
|
422
|
-
|
|
423
458
|
for i in range(3):
|
|
424
459
|
client.login()
|
|
425
|
-
response = client.post(
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
460
|
+
response = client.post(
|
|
461
|
+
url_for(
|
|
462
|
+
"oauth.authorize",
|
|
463
|
+
response_type="code",
|
|
464
|
+
client_id=oauth.client_id,
|
|
465
|
+
),
|
|
466
|
+
{
|
|
467
|
+
"scope": "default",
|
|
468
|
+
"accept": "",
|
|
469
|
+
},
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
uri, params = response.location.split("?")
|
|
473
|
+
code = parse_qs(params)["code"][0]
|
|
436
474
|
|
|
437
475
|
client.logout()
|
|
438
|
-
response = client.post(
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
476
|
+
response = client.post(
|
|
477
|
+
url_for("oauth.token"),
|
|
478
|
+
{
|
|
479
|
+
"grant_type": "authorization_code",
|
|
480
|
+
"code": code,
|
|
481
|
+
},
|
|
482
|
+
headers=basic_header(oauth),
|
|
483
|
+
)
|
|
442
484
|
|
|
443
485
|
assert200(response)
|
|
444
|
-
assert response.content_type ==
|
|
445
|
-
assert
|
|
486
|
+
assert response.content_type == "application/json"
|
|
487
|
+
assert "access_token" in response.json
|
|
446
488
|
|
|
447
489
|
def test_authorization_grant_token_body_credentials(self, client, oauth):
|
|
448
490
|
client.login()
|
|
449
491
|
|
|
450
|
-
response = client.post(
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
492
|
+
response = client.post(
|
|
493
|
+
url_for(
|
|
494
|
+
"oauth.authorize",
|
|
495
|
+
response_type="code",
|
|
496
|
+
client_id=oauth.client_id,
|
|
497
|
+
),
|
|
498
|
+
{
|
|
499
|
+
"scope": "default",
|
|
500
|
+
"accept": "",
|
|
501
|
+
},
|
|
502
|
+
)
|
|
458
503
|
|
|
459
|
-
uri, params = response.location.split(
|
|
460
|
-
code = parse_qs(params)[
|
|
504
|
+
uri, params = response.location.split("?")
|
|
505
|
+
code = parse_qs(params)["code"][0]
|
|
461
506
|
|
|
462
507
|
client.logout()
|
|
463
|
-
response = client.post(
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
508
|
+
response = client.post(
|
|
509
|
+
url_for("oauth.token"),
|
|
510
|
+
{
|
|
511
|
+
"grant_type": "authorization_code",
|
|
512
|
+
"code": code,
|
|
513
|
+
"client_id": oauth.client_id,
|
|
514
|
+
"client_secret": oauth.secret,
|
|
515
|
+
},
|
|
516
|
+
)
|
|
469
517
|
|
|
470
518
|
assert200(response)
|
|
471
|
-
assert response.content_type ==
|
|
472
|
-
assert
|
|
519
|
+
assert response.content_type == "application/json"
|
|
520
|
+
assert "access_token" in response.json
|
|
473
521
|
|
|
474
522
|
@pytest.mark.oauth(internal=True)
|
|
475
523
|
def test_authorization_redirects_for_internal_clients(self, client, oauth):
|
|
476
524
|
client.login()
|
|
477
525
|
|
|
478
|
-
response = client.get(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
526
|
+
response = client.get(
|
|
527
|
+
url_for(
|
|
528
|
+
"oauth.authorize",
|
|
529
|
+
response_type="code",
|
|
530
|
+
client_id=oauth.client_id,
|
|
531
|
+
redirect_uri=oauth.default_redirect_uri,
|
|
532
|
+
)
|
|
533
|
+
)
|
|
484
534
|
|
|
485
535
|
assert_status(response, 302)
|
|
486
|
-
uri, params = response.location.split(
|
|
536
|
+
uri, params = response.location.split("?")
|
|
487
537
|
|
|
488
538
|
assert uri == oauth.default_redirect_uri
|
|
489
|
-
assert
|
|
539
|
+
assert "code" in parse_qs(params)
|
|
490
540
|
|
|
491
541
|
def test_client_credentials_grant_token(self, client, oauth):
|
|
492
|
-
response = client.post(
|
|
493
|
-
|
|
494
|
-
|
|
542
|
+
response = client.post(
|
|
543
|
+
url_for("oauth.token"),
|
|
544
|
+
{
|
|
545
|
+
"grant_type": "client_credentials",
|
|
546
|
+
},
|
|
547
|
+
headers=basic_header(oauth),
|
|
548
|
+
)
|
|
495
549
|
|
|
496
550
|
assert200(response)
|
|
497
|
-
assert response.content_type ==
|
|
498
|
-
assert
|
|
551
|
+
assert response.content_type == "application/json"
|
|
552
|
+
assert "access_token" in response.json
|
|
499
553
|
|
|
500
554
|
def test_password_grant_token(self, client, oauth):
|
|
501
|
-
user = UserFactory(password=
|
|
502
|
-
|
|
503
|
-
response = client.post(
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
555
|
+
user = UserFactory(password="password")
|
|
556
|
+
|
|
557
|
+
response = client.post(
|
|
558
|
+
url_for("oauth.token"),
|
|
559
|
+
{
|
|
560
|
+
"grant_type": "password",
|
|
561
|
+
"username": user.email,
|
|
562
|
+
"password": "password",
|
|
563
|
+
},
|
|
564
|
+
headers=basic_header(oauth),
|
|
565
|
+
)
|
|
508
566
|
|
|
509
567
|
assert200(response)
|
|
510
|
-
assert response.content_type ==
|
|
511
|
-
assert
|
|
568
|
+
assert response.content_type == "application/json"
|
|
569
|
+
assert "access_token" in response.json
|
|
512
570
|
|
|
513
571
|
def test_invalid_implicit_grant_token(self, client, oauth):
|
|
514
572
|
client.login()
|
|
515
|
-
response = client.post(
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
573
|
+
response = client.post(
|
|
574
|
+
url_for(
|
|
575
|
+
"oauth.authorize",
|
|
576
|
+
response_type="token",
|
|
577
|
+
client_id=oauth.client_id,
|
|
578
|
+
),
|
|
579
|
+
{
|
|
580
|
+
"accept": "",
|
|
581
|
+
},
|
|
582
|
+
)
|
|
522
583
|
|
|
523
584
|
assert_status(response, 400)
|
|
524
|
-
assert response.json[
|
|
585
|
+
assert response.json["error"] == "invalid_grant"
|
|
525
586
|
|
|
526
587
|
@pytest.mark.oauth(confidential=True)
|
|
527
588
|
def test_refresh_token(self, client, oauth):
|
|
@@ -529,50 +590,59 @@ class APIAuthTest:
|
|
|
529
590
|
token = OAuth2Token.objects.create(
|
|
530
591
|
client=oauth,
|
|
531
592
|
user=user,
|
|
532
|
-
access_token=
|
|
533
|
-
refresh_token=
|
|
593
|
+
access_token="access-token",
|
|
594
|
+
refresh_token="refresh-token",
|
|
534
595
|
)
|
|
535
596
|
|
|
536
|
-
response = client.post(
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
597
|
+
response = client.post(
|
|
598
|
+
url_for("oauth.token"),
|
|
599
|
+
{
|
|
600
|
+
"grant_type": "refresh_token",
|
|
601
|
+
"refresh_token": token.refresh_token,
|
|
602
|
+
},
|
|
603
|
+
headers=basic_header(oauth),
|
|
604
|
+
)
|
|
540
605
|
|
|
541
606
|
assert200(response)
|
|
542
|
-
assert response.content_type ==
|
|
543
|
-
assert
|
|
607
|
+
assert response.content_type == "application/json"
|
|
608
|
+
assert "access_token" in response.json
|
|
544
609
|
|
|
545
|
-
@pytest.mark.parametrize(
|
|
610
|
+
@pytest.mark.parametrize("token_type", ["access_token", "refresh_token"])
|
|
546
611
|
def test_revoke_token(self, client, oauth, token_type):
|
|
547
612
|
user = UserFactory()
|
|
548
613
|
token = OAuth2Token.objects.create(
|
|
549
614
|
client=oauth,
|
|
550
615
|
user=user,
|
|
551
|
-
access_token=
|
|
552
|
-
refresh_token=
|
|
616
|
+
access_token="access-token",
|
|
617
|
+
refresh_token="refresh-token",
|
|
618
|
+
)
|
|
619
|
+
response = client.post(
|
|
620
|
+
url_for("oauth.revoke_token"),
|
|
621
|
+
{
|
|
622
|
+
"token": getattr(token, token_type),
|
|
623
|
+
},
|
|
624
|
+
headers=basic_header(oauth),
|
|
553
625
|
)
|
|
554
|
-
response = client.post(url_for('oauth.revoke_token'), {
|
|
555
|
-
'token': getattr(token, token_type),
|
|
556
|
-
}, headers=basic_header(oauth))
|
|
557
626
|
|
|
558
627
|
assert200(response)
|
|
559
628
|
|
|
560
629
|
tok = OAuth2Token.objects(pk=token.pk).first()
|
|
561
630
|
assert tok.revoked is True
|
|
562
631
|
|
|
563
|
-
@pytest.mark.parametrize(
|
|
632
|
+
@pytest.mark.parametrize("token_type", ["access_token", "refresh_token"])
|
|
564
633
|
def test_revoke_token_with_hint(self, client, oauth, token_type):
|
|
565
634
|
user = UserFactory()
|
|
566
635
|
token = OAuth2Token.objects.create(
|
|
567
636
|
client=oauth,
|
|
568
637
|
user=user,
|
|
569
|
-
access_token=
|
|
570
|
-
refresh_token=
|
|
638
|
+
access_token="access-token",
|
|
639
|
+
refresh_token="refresh-token",
|
|
640
|
+
)
|
|
641
|
+
response = client.post(
|
|
642
|
+
url_for("oauth.revoke_token"),
|
|
643
|
+
{"token": getattr(token, token_type), "token_type_hint": token_type},
|
|
644
|
+
headers=basic_header(oauth),
|
|
571
645
|
)
|
|
572
|
-
response = client.post(url_for('oauth.revoke_token'), {
|
|
573
|
-
'token': getattr(token, token_type),
|
|
574
|
-
'token_type_hint': token_type
|
|
575
|
-
}, headers=basic_header(oauth))
|
|
576
646
|
assert200(response)
|
|
577
647
|
|
|
578
648
|
tok = OAuth2Token.objects(pk=token.pk).first()
|
|
@@ -583,37 +653,41 @@ class APIAuthTest:
|
|
|
583
653
|
token = OAuth2Token.objects.create(
|
|
584
654
|
client=oauth,
|
|
585
655
|
user=user,
|
|
586
|
-
access_token=
|
|
587
|
-
refresh_token=
|
|
656
|
+
access_token="access-token",
|
|
657
|
+
refresh_token="refresh-token",
|
|
588
658
|
)
|
|
589
659
|
|
|
590
|
-
response = client.post(
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
660
|
+
response = client.post(
|
|
661
|
+
url_for("oauth.revoke_token"),
|
|
662
|
+
{
|
|
663
|
+
"token": token.access_token,
|
|
664
|
+
"token_type_hint": "refresh_token",
|
|
665
|
+
},
|
|
666
|
+
headers=basic_header(oauth),
|
|
667
|
+
)
|
|
594
668
|
assert200(response)
|
|
595
669
|
|
|
596
670
|
tok = OAuth2Token.objects(pk=token.pk).first()
|
|
597
671
|
assert tok.revoked is False
|
|
598
672
|
|
|
599
673
|
def test_value_error(self, api):
|
|
600
|
-
@ns.route(
|
|
674
|
+
@ns.route("/exception", endpoint="exception")
|
|
601
675
|
class ExceptionAPI(API):
|
|
602
676
|
def get(self):
|
|
603
|
-
raise ValueError(
|
|
677
|
+
raise ValueError("Not working")
|
|
604
678
|
|
|
605
|
-
response = api.get(url_for(
|
|
679
|
+
response = api.get(url_for("api.exception"))
|
|
606
680
|
|
|
607
681
|
assert400(response)
|
|
608
|
-
assert response.json[
|
|
682
|
+
assert response.json["message"] == "Not working"
|
|
609
683
|
|
|
610
684
|
def test_permission_denied(self, api):
|
|
611
|
-
@ns.route(
|
|
685
|
+
@ns.route("/exception", endpoint="exception")
|
|
612
686
|
class ExceptionAPI(API):
|
|
613
687
|
def get(self):
|
|
614
|
-
raise PermissionDenied(
|
|
688
|
+
raise PermissionDenied("Permission denied")
|
|
615
689
|
|
|
616
|
-
response = api.get(url_for(
|
|
690
|
+
response = api.get(url_for("api.exception"))
|
|
617
691
|
|
|
618
692
|
assert403(response)
|
|
619
|
-
assert
|
|
693
|
+
assert "message" in response.json
|