udata 13.0.1.dev12__py3-none-any.whl → 14.4.1.dev7__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.
- udata/api/__init__.py +2 -8
- udata/api_fields.py +35 -4
- udata/app.py +30 -50
- udata/auth/__init__.py +29 -6
- udata/auth/forms.py +8 -6
- udata/auth/views.py +6 -3
- udata/commands/__init__.py +2 -14
- udata/commands/db.py +13 -25
- udata/commands/info.py +0 -16
- udata/commands/serve.py +3 -11
- udata/commands/tests/test_fixtures.py +9 -9
- udata/core/access_type/api.py +1 -1
- udata/core/access_type/constants.py +12 -8
- udata/core/activity/api.py +5 -6
- udata/core/avatars/api.py +43 -0
- udata/core/avatars/test_avatar_api.py +30 -0
- udata/core/badges/tests/test_commands.py +6 -6
- udata/core/csv.py +5 -0
- udata/core/dataservices/models.py +15 -3
- udata/core/dataservices/tasks.py +7 -0
- udata/core/dataset/api.py +2 -0
- udata/core/dataset/models.py +2 -2
- udata/core/dataset/permissions.py +31 -0
- udata/core/dataset/tasks.py +50 -10
- udata/core/discussions/models.py +1 -0
- udata/core/metrics/__init__.py +0 -6
- udata/core/organization/api.py +8 -5
- udata/core/organization/mails.py +1 -1
- udata/core/organization/models.py +9 -1
- udata/core/organization/notifications.py +84 -0
- udata/core/organization/permissions.py +1 -1
- udata/core/organization/tasks.py +3 -0
- udata/core/pages/tests/test_api.py +32 -0
- udata/core/post/api.py +24 -69
- udata/core/post/models.py +84 -16
- udata/core/post/tests/test_api.py +24 -1
- udata/core/reports/api.py +18 -0
- udata/core/reports/models.py +42 -2
- udata/core/reuse/models.py +1 -1
- udata/core/reuse/tasks.py +7 -0
- udata/core/site/models.py +2 -6
- udata/core/spatial/commands.py +2 -4
- udata/core/spatial/forms.py +2 -2
- udata/core/spatial/models.py +0 -10
- udata/core/spatial/tests/test_api.py +1 -36
- udata/core/user/models.py +15 -2
- udata/cors.py +2 -5
- udata/db/migrations.py +279 -0
- udata/features/notifications/api.py +7 -18
- udata/features/notifications/models.py +56 -0
- udata/features/notifications/tasks.py +25 -0
- udata/flask_mongoengine/engine.py +0 -4
- udata/frontend/__init__.py +3 -122
- udata/frontend/markdown.py +2 -1
- udata/harvest/actions.py +24 -9
- udata/harvest/api.py +30 -22
- udata/harvest/backends/__init__.py +21 -9
- udata/harvest/backends/base.py +29 -3
- udata/harvest/backends/ckan/harvesters.py +13 -2
- udata/harvest/backends/dcat.py +3 -0
- udata/harvest/backends/maaf.py +1 -0
- udata/harvest/commands.py +39 -4
- udata/harvest/filters.py +17 -6
- udata/harvest/forms.py +9 -6
- udata/harvest/models.py +16 -0
- udata/harvest/permissions.py +27 -0
- udata/harvest/tasks.py +3 -5
- udata/harvest/tests/ckan/test_ckan_backend.py +35 -2
- udata/harvest/tests/ckan/test_ckan_backend_errors.py +1 -1
- udata/harvest/tests/ckan/test_ckan_backend_filters.py +1 -1
- udata/harvest/tests/ckan/test_dkan_backend.py +1 -1
- udata/harvest/tests/dcat/udata.xml +6 -6
- udata/harvest/tests/factories.py +1 -1
- udata/harvest/tests/test_actions.py +63 -8
- udata/harvest/tests/test_api.py +278 -123
- udata/harvest/tests/test_base_backend.py +88 -1
- udata/harvest/tests/test_dcat_backend.py +60 -13
- udata/harvest/tests/test_filters.py +6 -0
- udata/i18n.py +11 -273
- udata/mail.py +5 -1
- udata/migrations/2025-10-31-create-membership-request-notifications.py +55 -0
- udata/migrations/2025-11-13-delete-user-email-index.py +25 -0
- udata/migrations/2025-12-04-add-uuid-to-discussion-messages.py +28 -0
- udata/models/__init__.py +0 -8
- udata/mongo/slug_fields.py +1 -1
- udata/rdf.py +45 -6
- udata/routing.py +2 -10
- udata/sentry.py +4 -10
- udata/settings.py +23 -17
- udata/tasks.py +4 -3
- udata/templates/mail/message.html +5 -31
- udata/tests/__init__.py +28 -12
- udata/tests/api/__init__.py +108 -21
- udata/tests/api/test_activities_api.py +36 -0
- udata/tests/api/test_auth_api.py +121 -95
- udata/tests/api/test_base_api.py +7 -4
- udata/tests/api/test_dataservices_api.py +29 -1
- udata/tests/api/test_datasets_api.py +45 -21
- udata/tests/api/test_organizations_api.py +192 -197
- udata/tests/api/test_reports_api.py +157 -0
- udata/tests/api/test_reuses_api.py +147 -147
- udata/tests/api/test_security_api.py +12 -12
- udata/tests/api/test_swagger.py +4 -4
- udata/tests/api/test_tags_api.py +8 -8
- udata/tests/api/test_user_api.py +13 -1
- udata/tests/apiv2/test_swagger.py +4 -4
- udata/tests/apiv2/test_topics.py +1 -1
- udata/tests/cli/test_cli_base.py +8 -9
- udata/tests/dataset/test_dataset_commands.py +4 -4
- udata/tests/dataset/test_dataset_model.py +66 -26
- udata/tests/dataset/test_dataset_rdf.py +99 -5
- udata/tests/dataset/test_resource_preview.py +0 -1
- udata/tests/frontend/test_auth.py +24 -1
- udata/tests/frontend/test_csv.py +0 -3
- udata/tests/helpers.py +37 -27
- udata/tests/organization/test_notifications.py +67 -2
- udata/tests/plugin.py +6 -261
- udata/tests/site/test_site_csv_exports.py +22 -10
- udata/tests/test_activity.py +9 -9
- udata/tests/test_cors.py +1 -1
- udata/tests/test_dcat_commands.py +2 -2
- udata/tests/test_discussions.py +5 -5
- udata/tests/test_migrations.py +181 -481
- udata/tests/test_notifications.py +15 -57
- udata/tests/test_notifications_task.py +43 -0
- udata/tests/test_owned.py +81 -1
- udata/tests/test_storages.py +25 -19
- udata/tests/test_topics.py +77 -61
- udata/tests/test_uris.py +33 -0
- udata/tests/workers/test_jobs_commands.py +23 -23
- udata/translations/ar/LC_MESSAGES/udata.mo +0 -0
- udata/translations/ar/LC_MESSAGES/udata.po +187 -108
- udata/translations/de/LC_MESSAGES/udata.mo +0 -0
- udata/translations/de/LC_MESSAGES/udata.po +187 -108
- udata/translations/es/LC_MESSAGES/udata.mo +0 -0
- udata/translations/es/LC_MESSAGES/udata.po +187 -108
- udata/translations/fr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/fr/LC_MESSAGES/udata.po +188 -109
- udata/translations/it/LC_MESSAGES/udata.mo +0 -0
- udata/translations/it/LC_MESSAGES/udata.po +187 -108
- udata/translations/pt/LC_MESSAGES/udata.mo +0 -0
- udata/translations/pt/LC_MESSAGES/udata.po +187 -108
- udata/translations/sr/LC_MESSAGES/udata.mo +0 -0
- udata/translations/sr/LC_MESSAGES/udata.po +187 -108
- udata/translations/udata.pot +215 -106
- udata/uris.py +0 -2
- udata/utils.py +5 -0
- udata-14.4.1.dev7.dist-info/METADATA +109 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/RECORD +153 -166
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/entry_points.txt +3 -5
- udata/core/followers/views.py +0 -15
- udata/core/post/forms.py +0 -30
- udata/entrypoints.py +0 -93
- udata/features/identicon/__init__.py +0 -0
- udata/features/identicon/api.py +0 -13
- udata/features/identicon/backends.py +0 -131
- udata/features/identicon/tests/__init__.py +0 -0
- udata/features/identicon/tests/test_backends.py +0 -18
- udata/features/territories/__init__.py +0 -49
- udata/features/territories/api.py +0 -25
- udata/features/territories/models.py +0 -51
- udata/flask_mongoengine/json.py +0 -38
- udata/migrations/__init__.py +0 -367
- udata/templates/mail/base.html +0 -105
- udata/templates/mail/base.txt +0 -6
- udata/templates/mail/button.html +0 -3
- udata/templates/mail/layouts/1-column.html +0 -19
- udata/templates/mail/layouts/2-columns.html +0 -20
- udata/templates/mail/layouts/center-panel.html +0 -16
- udata/tests/cli/test_db_cli.py +0 -68
- udata/tests/features/territories/__init__.py +0 -20
- udata/tests/features/territories/test_territories_api.py +0 -185
- udata/tests/frontend/test_hooks.py +0 -149
- udata-13.0.1.dev12.dist-info/METADATA +0 -133
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/WHEEL +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/licenses/LICENSE +0 -0
- {udata-13.0.1.dev12.dist-info → udata-14.4.1.dev7.dist-info}/top_level.txt +0 -0
udata/tests/api/test_auth_api.py
CHANGED
|
@@ -67,34 +67,34 @@ def oauth(app, request):
|
|
|
67
67
|
|
|
68
68
|
|
|
69
69
|
class APIAuthTest(PytestOnlyAPITestCase):
|
|
70
|
-
def test_no_auth(self
|
|
70
|
+
def test_no_auth(self):
|
|
71
71
|
"""Should not return a content type if there is no content on delete"""
|
|
72
|
-
response =
|
|
72
|
+
response = self.get(url_for("api.fake"))
|
|
73
73
|
|
|
74
74
|
assert200(response)
|
|
75
75
|
assert response.content_type == "application/json"
|
|
76
76
|
assert response.json == {"success": True}
|
|
77
77
|
|
|
78
|
-
def test_session_auth(self
|
|
78
|
+
def test_session_auth(self):
|
|
79
79
|
"""Should handle session authentication"""
|
|
80
|
-
|
|
80
|
+
self.login() # Session auth
|
|
81
81
|
|
|
82
|
-
response =
|
|
82
|
+
response = self.post(url_for("api.fake"))
|
|
83
83
|
|
|
84
84
|
assert200(response)
|
|
85
85
|
assert response.content_type == "application/json"
|
|
86
86
|
assert response.json == {"success": True}
|
|
87
87
|
|
|
88
|
-
def test_header_auth(self
|
|
88
|
+
def test_header_auth(self):
|
|
89
89
|
"""Should handle header API Key authentication"""
|
|
90
|
-
with
|
|
91
|
-
response =
|
|
90
|
+
with self.api_user() as user: # API Key auth
|
|
91
|
+
response = self.post(url_for("api.fake"), headers={"X-API-KEY": user.apikey})
|
|
92
92
|
|
|
93
93
|
assert200(response)
|
|
94
94
|
assert response.content_type == "application/json"
|
|
95
95
|
assert response.json == {"success": True}
|
|
96
96
|
|
|
97
|
-
def test_oauth_auth(self,
|
|
97
|
+
def test_oauth_auth(self, oauth):
|
|
98
98
|
"""Should handle OAuth header authentication"""
|
|
99
99
|
user = UserFactory()
|
|
100
100
|
token = OAuth2Token.objects.create(
|
|
@@ -104,7 +104,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
104
104
|
refresh_token="refresh-token",
|
|
105
105
|
)
|
|
106
106
|
|
|
107
|
-
response =
|
|
107
|
+
response = self.post(
|
|
108
108
|
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token.access_token])}
|
|
109
109
|
)
|
|
110
110
|
|
|
@@ -112,7 +112,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
112
112
|
assert response.content_type == "application/json"
|
|
113
113
|
assert response.json == {"success": True}
|
|
114
114
|
|
|
115
|
-
def test_bad_oauth_auth(self,
|
|
115
|
+
def test_bad_oauth_auth(self, oauth):
|
|
116
116
|
"""Should handle wrong OAuth header authentication"""
|
|
117
117
|
user = UserFactory()
|
|
118
118
|
OAuth2Token.objects.create(
|
|
@@ -122,53 +122,53 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
122
122
|
refresh_token="refresh-token",
|
|
123
123
|
)
|
|
124
124
|
|
|
125
|
-
response =
|
|
125
|
+
response = self.post(
|
|
126
126
|
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", "not-my-token"])}
|
|
127
127
|
)
|
|
128
128
|
|
|
129
129
|
assert401(response)
|
|
130
130
|
assert response.content_type == "application/json"
|
|
131
131
|
|
|
132
|
-
def test_no_apikey(self
|
|
132
|
+
def test_no_apikey(self):
|
|
133
133
|
"""Should raise a HTTP 401 if no API Key is provided"""
|
|
134
|
-
response =
|
|
134
|
+
response = self.post(url_for("api.fake"))
|
|
135
135
|
|
|
136
136
|
assert401(response)
|
|
137
137
|
assert response.content_type == "application/json"
|
|
138
138
|
assert "message" in response.json
|
|
139
139
|
|
|
140
|
-
def test_invalid_apikey(self
|
|
140
|
+
def test_invalid_apikey(self):
|
|
141
141
|
"""Should raise a HTTP 401 if an invalid API Key is provided"""
|
|
142
|
-
response =
|
|
142
|
+
response = self.post(url_for("api.fake"), headers={"X-API-KEY": "fake"})
|
|
143
143
|
|
|
144
144
|
assert401(response)
|
|
145
145
|
assert response.content_type == "application/json"
|
|
146
146
|
assert "message" in response.json
|
|
147
147
|
|
|
148
|
-
def test_inactive_user(self
|
|
148
|
+
def test_inactive_user(self):
|
|
149
149
|
"""Should raise a HTTP 401 if the user is inactive"""
|
|
150
150
|
user = UserFactory(active=False)
|
|
151
|
-
with
|
|
152
|
-
response =
|
|
151
|
+
with self.api_user(user) as user:
|
|
152
|
+
response = self.post(url_for("api.fake"), headers={"X-API-KEY": user.apikey})
|
|
153
153
|
|
|
154
154
|
assert401(response)
|
|
155
155
|
assert response.content_type == "application/json"
|
|
156
156
|
assert "message" in response.json
|
|
157
157
|
|
|
158
|
-
def test_deleted_user(self
|
|
158
|
+
def test_deleted_user(self):
|
|
159
159
|
"""Should raise a HTTP 401 if the user is deleted"""
|
|
160
160
|
user = UserFactory()
|
|
161
161
|
user.mark_as_deleted()
|
|
162
|
-
with
|
|
163
|
-
response =
|
|
162
|
+
with self.api_user(user) as user:
|
|
163
|
+
response = self.post(url_for("api.fake"), headers={"X-API-KEY": user.apikey})
|
|
164
164
|
|
|
165
165
|
assert401(response)
|
|
166
166
|
assert response.content_type == "application/json"
|
|
167
167
|
assert "message" in response.json
|
|
168
168
|
|
|
169
|
-
def test_validation_errors(self
|
|
169
|
+
def test_validation_errors(self):
|
|
170
170
|
"""Should raise a HTTP 400 and returns errors on validation error"""
|
|
171
|
-
response =
|
|
171
|
+
response = self.put(url_for("api.fake"), {"email": "wrong"})
|
|
172
172
|
|
|
173
173
|
assert400(response)
|
|
174
174
|
assert response.content_type == "application/json"
|
|
@@ -177,9 +177,9 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
177
177
|
assert field in response.json["errors"]
|
|
178
178
|
assert isinstance(response.json["errors"][field], list)
|
|
179
179
|
|
|
180
|
-
def test_no_validation_error(self
|
|
180
|
+
def test_no_validation_error(self):
|
|
181
181
|
"""Should pass if no validation error"""
|
|
182
|
-
response =
|
|
182
|
+
response = self.put(
|
|
183
183
|
url_for("api.fake"),
|
|
184
184
|
{
|
|
185
185
|
"required": "value",
|
|
@@ -191,11 +191,11 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
191
191
|
assert200(response)
|
|
192
192
|
assert response.json == {"success": True}
|
|
193
193
|
|
|
194
|
-
def test_authorization_decline(self,
|
|
194
|
+
def test_authorization_decline(self, oauth):
|
|
195
195
|
"""Should redirect to the redirect_uri on authorization denied"""
|
|
196
|
-
|
|
196
|
+
self.login()
|
|
197
197
|
|
|
198
|
-
response =
|
|
198
|
+
response = self.post(
|
|
199
199
|
url_for(
|
|
200
200
|
"oauth.authorize",
|
|
201
201
|
response_type="code",
|
|
@@ -206,17 +206,18 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
206
206
|
"scope": "default",
|
|
207
207
|
"refuse": "",
|
|
208
208
|
},
|
|
209
|
+
json=False,
|
|
209
210
|
)
|
|
210
211
|
|
|
211
212
|
assert_status(response, 302)
|
|
212
213
|
uri, params = response.location.split("?")
|
|
213
214
|
assert uri == oauth.default_redirect_uri
|
|
214
215
|
|
|
215
|
-
def test_authorization_accept(self,
|
|
216
|
+
def test_authorization_accept(self, oauth):
|
|
216
217
|
"""Should redirect to the redirect_uri on authorization accepted"""
|
|
217
|
-
|
|
218
|
+
self.login()
|
|
218
219
|
|
|
219
|
-
response =
|
|
220
|
+
response = self.post(
|
|
220
221
|
url_for(
|
|
221
222
|
"oauth.authorize",
|
|
222
223
|
response_type="code",
|
|
@@ -227,6 +228,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
227
228
|
"scope": "default",
|
|
228
229
|
"accept": "",
|
|
229
230
|
},
|
|
231
|
+
json=False,
|
|
230
232
|
)
|
|
231
233
|
|
|
232
234
|
assert_status(response, 302)
|
|
@@ -236,14 +238,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
236
238
|
|
|
237
239
|
@pytest.mark.options(OAUTH2_ALLOW_WILDCARD_IN_REDIRECT_URI=True)
|
|
238
240
|
@pytest.mark.oauth(redirect_uris=["https://*.test.org/callback"])
|
|
239
|
-
def test_authorization_accept_wildcard(self,
|
|
241
|
+
def test_authorization_accept_wildcard(self, oauth):
|
|
240
242
|
"""Should redirect to the redirect_uri on authorization accepted
|
|
241
243
|
with wildcard enabled and used in config"""
|
|
242
|
-
|
|
244
|
+
self.login()
|
|
243
245
|
|
|
244
246
|
redirect_uri = "https://subdomain.test.org/callback"
|
|
245
247
|
|
|
246
|
-
response =
|
|
248
|
+
response = self.post(
|
|
247
249
|
url_for(
|
|
248
250
|
"oauth.authorize",
|
|
249
251
|
response_type="code",
|
|
@@ -254,6 +256,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
254
256
|
"scope": "default",
|
|
255
257
|
"accept": "",
|
|
256
258
|
},
|
|
259
|
+
json=False,
|
|
257
260
|
)
|
|
258
261
|
|
|
259
262
|
assert_status(response, 302)
|
|
@@ -263,14 +266,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
263
266
|
|
|
264
267
|
@pytest.mark.options(OAUTH2_ALLOW_WILDCARD_IN_REDIRECT_URI=False)
|
|
265
268
|
@pytest.mark.oauth(redirect_uris=["https://*.test.org/callback"])
|
|
266
|
-
def test_authorization_accept_no_wildcard(self,
|
|
269
|
+
def test_authorization_accept_no_wildcard(self, oauth):
|
|
267
270
|
"""Should not redirect to the redirect_uri on authorization accepted
|
|
268
271
|
without wildcard enabled while used in config"""
|
|
269
|
-
|
|
272
|
+
self.login()
|
|
270
273
|
|
|
271
274
|
redirect_uri = "https://subdomain.test.org/callback"
|
|
272
275
|
|
|
273
|
-
response =
|
|
276
|
+
response = self.post(
|
|
274
277
|
url_for(
|
|
275
278
|
"oauth.authorize",
|
|
276
279
|
response_type="code",
|
|
@@ -281,6 +284,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
281
284
|
"scope": "default",
|
|
282
285
|
"accept": "",
|
|
283
286
|
},
|
|
287
|
+
json=False,
|
|
284
288
|
)
|
|
285
289
|
|
|
286
290
|
assert_status(response, 400)
|
|
@@ -289,14 +293,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
289
293
|
|
|
290
294
|
@pytest.mark.options(OAUTH2_ALLOW_WILDCARD_IN_REDIRECT_URI=True)
|
|
291
295
|
@pytest.mark.oauth(redirect_uris=["https://*.test.org/callback"])
|
|
292
|
-
def test_authorization_accept_wrong_wildcard(self,
|
|
296
|
+
def test_authorization_accept_wrong_wildcard(self, oauth):
|
|
293
297
|
"""Should not redirect to the redirect_uri on authorization accepted
|
|
294
298
|
with wildcard enabled but mismatched from config"""
|
|
295
|
-
|
|
299
|
+
self.login()
|
|
296
300
|
|
|
297
301
|
redirect_uri = "https://subdomain.example.com/callback"
|
|
298
302
|
|
|
299
|
-
response =
|
|
303
|
+
response = self.post(
|
|
300
304
|
url_for(
|
|
301
305
|
"oauth.authorize",
|
|
302
306
|
response_type="code",
|
|
@@ -307,16 +311,17 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
307
311
|
"scope": "default",
|
|
308
312
|
"accept": "",
|
|
309
313
|
},
|
|
314
|
+
json=False,
|
|
310
315
|
)
|
|
311
316
|
|
|
312
317
|
assert_status(response, 400)
|
|
313
318
|
assert "error" in response.json
|
|
314
319
|
assert "Redirect URI" in response.json["error_description"]
|
|
315
320
|
|
|
316
|
-
def test_authorization_grant_token(self,
|
|
317
|
-
|
|
321
|
+
def test_authorization_grant_token(self, oauth):
|
|
322
|
+
self.login()
|
|
318
323
|
|
|
319
|
-
response =
|
|
324
|
+
response = self.post(
|
|
320
325
|
url_for(
|
|
321
326
|
"oauth.authorize",
|
|
322
327
|
response_type="code",
|
|
@@ -326,19 +331,21 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
326
331
|
"scope": "default",
|
|
327
332
|
"accept": "",
|
|
328
333
|
},
|
|
334
|
+
json=False,
|
|
329
335
|
)
|
|
330
336
|
|
|
331
337
|
uri, params = response.location.split("?")
|
|
332
338
|
code = parse_qs(params)["code"][0]
|
|
333
339
|
|
|
334
|
-
|
|
335
|
-
response =
|
|
340
|
+
self.logout()
|
|
341
|
+
response = self.post(
|
|
336
342
|
url_for("oauth.token"),
|
|
337
343
|
{
|
|
338
344
|
"grant_type": "authorization_code",
|
|
339
345
|
"code": code,
|
|
340
346
|
},
|
|
341
347
|
headers=basic_header(oauth),
|
|
348
|
+
json=False,
|
|
342
349
|
)
|
|
343
350
|
|
|
344
351
|
assert200(response)
|
|
@@ -347,13 +354,13 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
347
354
|
tokens = OAuth2Token.objects(access_token=response.json["access_token"])
|
|
348
355
|
assert len(tokens) == 1 # A token has been created and saved.
|
|
349
356
|
|
|
350
|
-
def test_s256_code_challenge_success_client_secret_basic(self,
|
|
357
|
+
def test_s256_code_challenge_success_client_secret_basic(self, oauth):
|
|
351
358
|
code_verifier = generate_token(48)
|
|
352
359
|
code_challenge = create_s256_code_challenge(code_verifier)
|
|
353
360
|
|
|
354
|
-
|
|
361
|
+
self.login()
|
|
355
362
|
|
|
356
|
-
response =
|
|
363
|
+
response = self.post(
|
|
357
364
|
url_for(
|
|
358
365
|
"oauth.authorize",
|
|
359
366
|
response_type="code",
|
|
@@ -365,16 +372,18 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
365
372
|
"scope": "default",
|
|
366
373
|
"accept": "",
|
|
367
374
|
},
|
|
375
|
+
json=False,
|
|
368
376
|
)
|
|
369
377
|
assert "code=" in response.location
|
|
370
378
|
|
|
371
379
|
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
372
380
|
code = params["code"]
|
|
373
381
|
|
|
374
|
-
response =
|
|
382
|
+
response = self.post(
|
|
375
383
|
url_for("oauth.token"),
|
|
376
384
|
{"grant_type": "authorization_code", "code": code, "code_verifier": code_verifier},
|
|
377
385
|
headers=basic_header(oauth),
|
|
386
|
+
json=False,
|
|
378
387
|
)
|
|
379
388
|
|
|
380
389
|
assert200(response)
|
|
@@ -383,7 +392,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
383
392
|
|
|
384
393
|
token = response.json["access_token"]
|
|
385
394
|
|
|
386
|
-
response =
|
|
395
|
+
response = self.post(
|
|
387
396
|
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token])}
|
|
388
397
|
)
|
|
389
398
|
|
|
@@ -391,13 +400,13 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
391
400
|
assert response.content_type == "application/json"
|
|
392
401
|
assert response.json == {"success": True}
|
|
393
402
|
|
|
394
|
-
def test_s256_code_challenge_success_client_secret_post(self,
|
|
403
|
+
def test_s256_code_challenge_success_client_secret_post(self, oauth):
|
|
395
404
|
code_verifier = generate_token(48)
|
|
396
405
|
code_challenge = create_s256_code_challenge(code_verifier)
|
|
397
406
|
|
|
398
|
-
|
|
407
|
+
self.login()
|
|
399
408
|
|
|
400
|
-
response =
|
|
409
|
+
response = self.post(
|
|
401
410
|
url_for(
|
|
402
411
|
"oauth.authorize",
|
|
403
412
|
response_type="code",
|
|
@@ -409,13 +418,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
409
418
|
"scope": "default",
|
|
410
419
|
"accept": "",
|
|
411
420
|
},
|
|
421
|
+
json=False,
|
|
412
422
|
)
|
|
413
423
|
assert "code=" in response.location
|
|
414
424
|
|
|
415
425
|
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
416
426
|
code = params["code"]
|
|
417
427
|
|
|
418
|
-
response =
|
|
428
|
+
response = self.post(
|
|
419
429
|
url_for("oauth.token"),
|
|
420
430
|
{
|
|
421
431
|
"grant_type": "authorization_code",
|
|
@@ -424,6 +434,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
424
434
|
"client_id": oauth.client_id,
|
|
425
435
|
"client_secret": oauth.secret,
|
|
426
436
|
},
|
|
437
|
+
json=False,
|
|
427
438
|
)
|
|
428
439
|
|
|
429
440
|
assert200(response)
|
|
@@ -432,7 +443,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
432
443
|
|
|
433
444
|
token = response.json["access_token"]
|
|
434
445
|
|
|
435
|
-
response =
|
|
446
|
+
response = self.post(
|
|
436
447
|
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token])}
|
|
437
448
|
)
|
|
438
449
|
|
|
@@ -441,14 +452,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
441
452
|
assert response.json == {"success": True}
|
|
442
453
|
|
|
443
454
|
@pytest.mark.oauth(secret=None)
|
|
444
|
-
def test_s256_code_challenge_success_no_client_secret(self,
|
|
455
|
+
def test_s256_code_challenge_success_no_client_secret(self, oauth):
|
|
445
456
|
"""Authenticate through an OAuth client that has no secret associated (public client)"""
|
|
446
457
|
code_verifier = generate_token(48)
|
|
447
458
|
code_challenge = create_s256_code_challenge(code_verifier)
|
|
448
459
|
|
|
449
|
-
|
|
460
|
+
self.login()
|
|
450
461
|
|
|
451
|
-
response =
|
|
462
|
+
response = self.post(
|
|
452
463
|
url_for(
|
|
453
464
|
"oauth.authorize",
|
|
454
465
|
response_type="code",
|
|
@@ -460,13 +471,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
460
471
|
"scope": "default",
|
|
461
472
|
"accept": "",
|
|
462
473
|
},
|
|
474
|
+
json=False,
|
|
463
475
|
)
|
|
464
476
|
assert "code=" in response.location
|
|
465
477
|
|
|
466
478
|
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
467
479
|
code = params["code"]
|
|
468
480
|
|
|
469
|
-
response =
|
|
481
|
+
response = self.post(
|
|
470
482
|
url_for("oauth.token"),
|
|
471
483
|
{
|
|
472
484
|
"grant_type": "authorization_code",
|
|
@@ -474,6 +486,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
474
486
|
"code_verifier": code_verifier,
|
|
475
487
|
"client_id": oauth.client_id,
|
|
476
488
|
},
|
|
489
|
+
json=False,
|
|
477
490
|
)
|
|
478
491
|
|
|
479
492
|
assert200(response)
|
|
@@ -482,7 +495,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
482
495
|
|
|
483
496
|
token = response.json["access_token"]
|
|
484
497
|
|
|
485
|
-
response =
|
|
498
|
+
response = self.post(
|
|
486
499
|
url_for("api.fake"), headers={"Authorization": " ".join(["Bearer", token])}
|
|
487
500
|
)
|
|
488
501
|
|
|
@@ -490,14 +503,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
490
503
|
assert response.content_type == "application/json"
|
|
491
504
|
assert response.json == {"success": True}
|
|
492
505
|
|
|
493
|
-
def test_s256_code_challenge_missing_client_secret(self,
|
|
506
|
+
def test_s256_code_challenge_missing_client_secret(self, oauth):
|
|
494
507
|
"""Fail authentication through an OAuth client with missing secret"""
|
|
495
508
|
code_verifier = generate_token(48)
|
|
496
509
|
code_challenge = create_s256_code_challenge(code_verifier)
|
|
497
510
|
|
|
498
|
-
|
|
511
|
+
self.login()
|
|
499
512
|
|
|
500
|
-
response =
|
|
513
|
+
response = self.post(
|
|
501
514
|
url_for(
|
|
502
515
|
"oauth.authorize",
|
|
503
516
|
response_type="code",
|
|
@@ -509,13 +522,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
509
522
|
"scope": "default",
|
|
510
523
|
"accept": "",
|
|
511
524
|
},
|
|
525
|
+
json=False,
|
|
512
526
|
)
|
|
513
527
|
assert "code=" in response.location
|
|
514
528
|
|
|
515
529
|
params = dict(url_decode(urlparse.urlparse(response.location).query))
|
|
516
530
|
code = params["code"]
|
|
517
531
|
|
|
518
|
-
response =
|
|
532
|
+
response = self.post(
|
|
519
533
|
url_for("oauth.token"),
|
|
520
534
|
{
|
|
521
535
|
"grant_type": "authorization_code",
|
|
@@ -523,14 +537,15 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
523
537
|
"code_verifier": code_verifier,
|
|
524
538
|
"client_id": oauth.client_id,
|
|
525
539
|
},
|
|
540
|
+
json=False,
|
|
526
541
|
)
|
|
527
542
|
|
|
528
543
|
assert401(response)
|
|
529
544
|
|
|
530
|
-
def test_authorization_multiple_grant_token(self,
|
|
545
|
+
def test_authorization_multiple_grant_token(self, oauth):
|
|
531
546
|
for i in range(3):
|
|
532
|
-
|
|
533
|
-
response =
|
|
547
|
+
self.login()
|
|
548
|
+
response = self.post(
|
|
534
549
|
url_for(
|
|
535
550
|
"oauth.authorize",
|
|
536
551
|
response_type="code",
|
|
@@ -540,29 +555,31 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
540
555
|
"scope": "default",
|
|
541
556
|
"accept": "",
|
|
542
557
|
},
|
|
558
|
+
json=False,
|
|
543
559
|
)
|
|
544
560
|
|
|
545
561
|
uri, params = response.location.split("?")
|
|
546
562
|
code = parse_qs(params)["code"][0]
|
|
547
563
|
|
|
548
|
-
|
|
549
|
-
response =
|
|
564
|
+
self.logout()
|
|
565
|
+
response = self.post(
|
|
550
566
|
url_for("oauth.token"),
|
|
551
567
|
{
|
|
552
568
|
"grant_type": "authorization_code",
|
|
553
569
|
"code": code,
|
|
554
570
|
},
|
|
555
571
|
headers=basic_header(oauth),
|
|
572
|
+
json=False,
|
|
556
573
|
)
|
|
557
574
|
|
|
558
575
|
assert200(response)
|
|
559
576
|
assert response.content_type == "application/json"
|
|
560
577
|
assert "access_token" in response.json
|
|
561
578
|
|
|
562
|
-
def test_authorization_grant_token_body_credentials(self,
|
|
563
|
-
|
|
579
|
+
def test_authorization_grant_token_body_credentials(self, oauth):
|
|
580
|
+
self.login()
|
|
564
581
|
|
|
565
|
-
response =
|
|
582
|
+
response = self.post(
|
|
566
583
|
url_for(
|
|
567
584
|
"oauth.authorize",
|
|
568
585
|
response_type="code",
|
|
@@ -572,13 +589,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
572
589
|
"scope": "default",
|
|
573
590
|
"accept": "",
|
|
574
591
|
},
|
|
592
|
+
json=False,
|
|
575
593
|
)
|
|
576
594
|
|
|
577
595
|
uri, params = response.location.split("?")
|
|
578
596
|
code = parse_qs(params)["code"][0]
|
|
579
597
|
|
|
580
|
-
|
|
581
|
-
response =
|
|
598
|
+
self.logout()
|
|
599
|
+
response = self.post(
|
|
582
600
|
url_for("oauth.token"),
|
|
583
601
|
{
|
|
584
602
|
"grant_type": "authorization_code",
|
|
@@ -586,6 +604,7 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
586
604
|
"client_id": oauth.client_id,
|
|
587
605
|
"client_secret": oauth.secret,
|
|
588
606
|
},
|
|
607
|
+
json=False,
|
|
589
608
|
)
|
|
590
609
|
|
|
591
610
|
assert200(response)
|
|
@@ -593,10 +612,10 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
593
612
|
assert "access_token" in response.json
|
|
594
613
|
|
|
595
614
|
@pytest.mark.oauth(internal=True)
|
|
596
|
-
def test_authorization_redirects_for_internal_clients(self,
|
|
597
|
-
|
|
615
|
+
def test_authorization_redirects_for_internal_clients(self, oauth):
|
|
616
|
+
self.login()
|
|
598
617
|
|
|
599
|
-
response =
|
|
618
|
+
response = self.get(
|
|
600
619
|
url_for(
|
|
601
620
|
"oauth.authorize",
|
|
602
621
|
response_type="code",
|
|
@@ -611,23 +630,24 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
611
630
|
assert uri == oauth.default_redirect_uri
|
|
612
631
|
assert "code" in parse_qs(params)
|
|
613
632
|
|
|
614
|
-
def test_client_credentials_grant_token(self,
|
|
615
|
-
response =
|
|
633
|
+
def test_client_credentials_grant_token(self, oauth):
|
|
634
|
+
response = self.post(
|
|
616
635
|
url_for("oauth.token"),
|
|
617
636
|
{
|
|
618
637
|
"grant_type": "client_credentials",
|
|
619
638
|
},
|
|
620
639
|
headers=basic_header(oauth),
|
|
640
|
+
json=False,
|
|
621
641
|
)
|
|
622
642
|
|
|
623
643
|
assert200(response)
|
|
624
644
|
assert response.content_type == "application/json"
|
|
625
645
|
assert "access_token" in response.json
|
|
626
646
|
|
|
627
|
-
def test_password_grant_token(self,
|
|
647
|
+
def test_password_grant_token(self, oauth):
|
|
628
648
|
user = UserFactory(password="password")
|
|
629
649
|
|
|
630
|
-
response =
|
|
650
|
+
response = self.post(
|
|
631
651
|
url_for("oauth.token"),
|
|
632
652
|
{
|
|
633
653
|
"grant_type": "password",
|
|
@@ -635,15 +655,16 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
635
655
|
"password": "password",
|
|
636
656
|
},
|
|
637
657
|
headers=basic_header(oauth),
|
|
658
|
+
json=False,
|
|
638
659
|
)
|
|
639
660
|
|
|
640
661
|
assert200(response)
|
|
641
662
|
assert response.content_type == "application/json"
|
|
642
663
|
assert "access_token" in response.json
|
|
643
664
|
|
|
644
|
-
def test_invalid_implicit_grant_token(self,
|
|
645
|
-
|
|
646
|
-
response =
|
|
665
|
+
def test_invalid_implicit_grant_token(self, oauth):
|
|
666
|
+
self.login()
|
|
667
|
+
response = self.post(
|
|
647
668
|
url_for(
|
|
648
669
|
"oauth.authorize",
|
|
649
670
|
response_type="token",
|
|
@@ -652,13 +673,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
652
673
|
{
|
|
653
674
|
"accept": "",
|
|
654
675
|
},
|
|
676
|
+
json=False,
|
|
655
677
|
)
|
|
656
678
|
|
|
657
679
|
assert_status(response, 400)
|
|
658
680
|
assert response.json["error"] == "unsupported_response_type"
|
|
659
681
|
|
|
660
682
|
@pytest.mark.oauth(confidential=True)
|
|
661
|
-
def test_refresh_token(self,
|
|
683
|
+
def test_refresh_token(self, oauth):
|
|
662
684
|
user = UserFactory()
|
|
663
685
|
token_to_be_refreshed = OAuth2Token.objects.create(
|
|
664
686
|
client=oauth,
|
|
@@ -680,13 +702,14 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
680
702
|
)
|
|
681
703
|
tokens_count = OAuth2Token.objects.count()
|
|
682
704
|
|
|
683
|
-
response =
|
|
705
|
+
response = self.post(
|
|
684
706
|
url_for("oauth.token"),
|
|
685
707
|
{
|
|
686
708
|
"grant_type": "refresh_token",
|
|
687
709
|
"refresh_token": token_to_be_refreshed.refresh_token,
|
|
688
710
|
},
|
|
689
711
|
headers=basic_header(oauth),
|
|
712
|
+
json=False,
|
|
690
713
|
)
|
|
691
714
|
|
|
692
715
|
assert200(response)
|
|
@@ -722,12 +745,13 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
722
745
|
access_token="access-token",
|
|
723
746
|
refresh_token="refresh-token",
|
|
724
747
|
)
|
|
725
|
-
response =
|
|
748
|
+
response = self.post(
|
|
726
749
|
url_for("oauth.revoke_token"),
|
|
727
750
|
{
|
|
728
751
|
"token": getattr(token, token_type),
|
|
729
752
|
},
|
|
730
753
|
headers=basic_header(oauth),
|
|
754
|
+
json=False,
|
|
731
755
|
)
|
|
732
756
|
|
|
733
757
|
assert200(response)
|
|
@@ -744,17 +768,18 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
744
768
|
access_token="access-token",
|
|
745
769
|
refresh_token="refresh-token",
|
|
746
770
|
)
|
|
747
|
-
response =
|
|
771
|
+
response = self.post(
|
|
748
772
|
url_for("oauth.revoke_token"),
|
|
749
773
|
{"token": getattr(token, token_type), "token_type_hint": token_type},
|
|
750
774
|
headers=basic_header(oauth),
|
|
775
|
+
json=False,
|
|
751
776
|
)
|
|
752
777
|
assert200(response)
|
|
753
778
|
|
|
754
779
|
tok = OAuth2Token.objects(pk=token.pk).first()
|
|
755
780
|
assert tok.revoked is True
|
|
756
781
|
|
|
757
|
-
def test_revoke_token_with_bad_hint(self,
|
|
782
|
+
def test_revoke_token_with_bad_hint(self, oauth):
|
|
758
783
|
user = UserFactory()
|
|
759
784
|
token = OAuth2Token.objects.create(
|
|
760
785
|
client=oauth,
|
|
@@ -763,37 +788,38 @@ class APIAuthTest(PytestOnlyAPITestCase):
|
|
|
763
788
|
refresh_token="refresh-token",
|
|
764
789
|
)
|
|
765
790
|
|
|
766
|
-
response =
|
|
791
|
+
response = self.post(
|
|
767
792
|
url_for("oauth.revoke_token"),
|
|
768
793
|
{
|
|
769
794
|
"token": token.access_token,
|
|
770
795
|
"token_type_hint": "refresh_token",
|
|
771
796
|
},
|
|
772
797
|
headers=basic_header(oauth),
|
|
798
|
+
json=False,
|
|
773
799
|
)
|
|
774
800
|
assert200(response)
|
|
775
801
|
|
|
776
802
|
tok = OAuth2Token.objects(pk=token.pk).first()
|
|
777
803
|
assert tok.revoked is False
|
|
778
804
|
|
|
779
|
-
def test_value_error(self
|
|
805
|
+
def test_value_error(self):
|
|
780
806
|
@ns.route("/exception", endpoint="exception")
|
|
781
807
|
class ExceptionAPI(API):
|
|
782
808
|
def get(self):
|
|
783
809
|
raise ValueError("Not working")
|
|
784
810
|
|
|
785
|
-
response =
|
|
811
|
+
response = self.get(url_for("api.exception"))
|
|
786
812
|
|
|
787
813
|
assert400(response)
|
|
788
814
|
assert response.json["message"] == "Not working"
|
|
789
815
|
|
|
790
|
-
def test_permission_denied(self
|
|
816
|
+
def test_permission_denied(self):
|
|
791
817
|
@ns.route("/exception", endpoint="exception")
|
|
792
818
|
class ExceptionAPI(API):
|
|
793
819
|
def get(self):
|
|
794
820
|
raise PermissionDenied("Permission denied")
|
|
795
821
|
|
|
796
|
-
response =
|
|
822
|
+
response = self.get(url_for("api.exception"))
|
|
797
823
|
|
|
798
824
|
assert403(response)
|
|
799
825
|
assert "message" in response.json
|