vantage6-algorithm-store 4.15.1__tar.gz → 4.15.2__tar.gz

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.
Files changed (59) hide show
  1. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/PKG-INFO +3 -3
  2. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_algorithm.py +102 -0
  3. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/_version.py +1 -1
  4. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/__init__.py +121 -21
  5. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/algorithm.py +13 -1
  6. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/PKG-INFO +3 -3
  7. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/requires.txt +2 -2
  8. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/setup.cfg +0 -0
  9. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/setup.py +0 -0
  10. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/__init__.py +0 -0
  11. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/base/__init__.py +0 -0
  12. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/base/unittest_base.py +0 -0
  13. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/__init__.py +0 -0
  14. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_policy.py +0 -0
  15. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_review.py +0 -0
  16. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_role.py +0 -0
  17. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_rule.py +0 -0
  18. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_user.py +0 -0
  19. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_v6server.py +0 -0
  20. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_version.py +0 -0
  21. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/test___init__.py +0 -0
  22. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/test_permission.py +0 -0
  23. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/__build__ +0 -0
  24. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/__init__.py +0 -0
  25. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/db.py +0 -0
  26. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/default_roles.py +0 -0
  27. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/globals.py +0 -0
  28. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/__init__.py +0 -0
  29. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/algorithm.py +0 -0
  30. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/allowed_argument_value.py +0 -0
  31. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/argument.py +0 -0
  32. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/base.py +0 -0
  33. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/common/enums.py +0 -0
  34. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/common/ui_visualization_schemas.py +0 -0
  35. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/database.py +0 -0
  36. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/function.py +0 -0
  37. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/permission.py +0 -0
  38. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/policy.py +0 -0
  39. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/review.py +0 -0
  40. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/role.py +0 -0
  41. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/role_rule_association.py +0 -0
  42. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/rule.py +0 -0
  43. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/ui_visualization.py +0 -0
  44. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/user.py +0 -0
  45. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/vantage6_server.py +0 -0
  46. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/permission.py +0 -0
  47. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/policy.py +0 -0
  48. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/review.py +0 -0
  49. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/role.py +0 -0
  50. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/rule.py +0 -0
  51. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/schema/input_schema.py +0 -0
  52. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/schema/output_schema.py +0 -0
  53. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/user.py +0 -0
  54. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/vantage6_server.py +0 -0
  55. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/version.py +0 -0
  56. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/wsgi.py +0 -0
  57. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/SOURCES.txt +0 -0
  58. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/dependency_links.txt +0 -0
  59. {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vantage6_algorithm_store
3
- Version: 4.15.1
3
+ Version: 4.15.2
4
4
  Summary: Vantage6 algorithm store
5
5
  Home-page: https://github.com/vantage6/vantage6
6
6
  Requires-Python: >=3.10
@@ -17,8 +17,8 @@ Requires-Dist: requests==2.33.1
17
17
  Requires-Dist: schema==0.7.5
18
18
  Requires-Dist: SQLAlchemy==1.4.46
19
19
  Requires-Dist: werkzeug==3.1.8
20
- Requires-Dist: vantage6==4.15.1
21
- Requires-Dist: vantage6-common==4.15.1
20
+ Requires-Dist: vantage6==4.15.2
21
+ Requires-Dist: vantage6-common==4.15.2
22
22
  Provides-Extra: dev
23
23
  Requires-Dist: coverage==6.4.4; extra == "dev"
24
24
  Dynamic: description
@@ -157,6 +157,83 @@ class TestAlgorithmResources(TestResources):
157
157
  whitelisted_server.delete()
158
158
  user.delete()
159
159
 
160
+ @patch("vantage6.algorithm.store.resource.request_validate_node_token")
161
+ def test_view_algorithm_decorator_node(self, validate_node_token_mock):
162
+ """
163
+ Test that a request self-identifying as a node (Client-Type: node header) is
164
+ allowed to list approved algorithms (`allow_node=True` on the decorator) once
165
+ its own server confirms the token, even under the store's most restrictive
166
+ algorithm_view policy and without any store User being registered - and that
167
+ this does not extend to non-approved algorithms.
168
+ """
169
+ # even the most restrictive policy should not block a node
170
+ policy = Policy(
171
+ key=StorePolicies.ALGORITHM_VIEW,
172
+ value=AlgorithmViewPolicies.ONLY_WITH_EXPLICIT_PERMISSION,
173
+ )
174
+ policy.save()
175
+ whitelisted_server = self.register_server()
176
+ node_headers = {
177
+ "server_url": SERVER_URL,
178
+ "Authorization": "Mock",
179
+ "Client-Type": "node",
180
+ }
181
+
182
+ # the node's own server says the token is not valid -> denied
183
+ validate_node_token_mock.return_value = (
184
+ MockResponse(),
185
+ HTTPStatus.UNAUTHORIZED,
186
+ )
187
+ rv = self.app.get("/api/algorithm", headers=node_headers)
188
+ self.assertNotEqual(rv.status_code, HTTPStatus.OK)
189
+
190
+ # the node's own server confirms the token is valid -> allowed
191
+ validate_node_token_mock.return_value = (MockResponse(), HTTPStatus.OK)
192
+ rv = self.app.get("/api/algorithm", headers=node_headers)
193
+ self.assertEqual(rv.status_code, HTTPStatus.OK)
194
+
195
+ # a node cannot use this to see non-approved algorithms
196
+ for arg in [
197
+ "awaiting_reviewer_assignment",
198
+ "under_review",
199
+ "in_review_process",
200
+ "invalidated",
201
+ ]:
202
+ rv = self.app.get(f"/api/algorithm?{arg}=1", headers=node_headers)
203
+ self.assertNotEqual(rv.status_code, HTTPStatus.OK)
204
+
205
+ # this is not a blanket bypass: a request without the Client-Type header is
206
+ # still governed by the normal (server/user-based) auth flow, which under
207
+ # this policy requires a registered, permissioned store user
208
+ rv = self.app.get("/api/algorithm", headers=HEADERS)
209
+ self.assertNotEqual(rv.status_code, HTTPStatus.OK)
210
+
211
+ # cleanup
212
+ policy.delete()
213
+ whitelisted_server.delete()
214
+
215
+ def test_view_algorithm_single_not_affected_by_node_bypass(self):
216
+ """
217
+ Test that the node bypass on the algorithm list endpoint does not extend to
218
+ /api/algorithm/<id>, which was deliberately left on the un-modified
219
+ decorator.
220
+ """
221
+ algorithm = Algorithm(name="test_algorithm", status=AlgorithmStatus.APPROVED)
222
+ algorithm.save()
223
+
224
+ node_headers = {
225
+ "server_url": SERVER_URL,
226
+ "Authorization": "Mock",
227
+ "Client-Type": "node",
228
+ }
229
+ # no policy is defined, so the default applies - and a node token is not
230
+ # recognized at all here, since this endpoint never got `allow_node=True`
231
+ rv = self.app.get(f"/api/algorithm/{algorithm.id}", headers=node_headers)
232
+ self.assertNotEqual(rv.status_code, HTTPStatus.OK)
233
+
234
+ # cleanup
235
+ algorithm.delete()
236
+
160
237
  @patch("vantage6.algorithm.store.resource.request_validate_server_token")
161
238
  def test_algorithm_view_multi(self, validate_token_mock):
162
239
  """Test GET /api/algorithm"""
@@ -562,6 +639,31 @@ class TestAlgorithmResources(TestResources):
562
639
  self.assertEqual(response.status_code, 200)
563
640
  self.assertEqual(response.json["description"], "new_description")
564
641
 
642
+ # check that another developer cannot update this algorithm - they are
643
+ # not its owner
644
+ other_username = "other_developer"
645
+ self.register_user(
646
+ server.id,
647
+ username=other_username,
648
+ user_rules=[Rule.get_by_("algorithm", Operation.EDIT)],
649
+ )
650
+ validate_token_mock.return_value = (
651
+ MockResponse({"username": other_username}),
652
+ HTTPStatus.OK,
653
+ )
654
+ response = self.app.patch(
655
+ f"/api/algorithm/{algorithm.id}",
656
+ json={"description": "other_description"},
657
+ headers=HEADERS,
658
+ )
659
+ self.assertEqual(response.status_code, 403)
660
+
661
+ # switch back to the algorithm's own developer for the remaining checks
662
+ validate_token_mock.return_value = (
663
+ MockResponse({"username": USERNAME}),
664
+ HTTPStatus.OK,
665
+ )
666
+
565
667
  # check that algorithm cannot be updated if it is approved
566
668
  algorithm.status = AlgorithmStatus.APPROVED
567
669
  algorithm.approved_at = datetime.datetime.now(datetime.timezone.utc)
@@ -7,7 +7,7 @@ with open(os.path.join(here, "__build__")) as fp:
7
7
  __build__ = json.load(fp)
8
8
 
9
9
  # Module version
10
- version_info = (4, 15, 1, "final", __build__, 0)
10
+ version_info = (4, 15, 2, "final", __build__, 0)
11
11
 
12
12
  # Module version stage suffix map
13
13
  _specifier_ = {"alpha": "a", "beta": "b", "candidate": "rc", "final": ""}
@@ -1,25 +1,26 @@
1
- import os
2
1
  import logging
3
- import requests
2
+ import os
3
+ from collections.abc import Callable
4
4
  from functools import wraps
5
5
  from http import HTTPStatus
6
- from flask import Response, request, current_app, g
6
+
7
+ import requests
8
+ from flask import Response, current_app, g, request
7
9
  from flask_mail import Mail
8
10
  from flask_principal import Identity, identity_changed
9
11
  from flask_restful import Api
10
-
11
12
  from vantage6.algorithm.store import PermissionManager
12
- from vantage6.algorithm.store.model.rule import Operation
13
- from vantage6.common import logger_name
14
- from vantage6.common.enum import AlgorithmViewPolicies, StorePolicies
15
- from vantage6.algorithm.store.model.vantage6_server import Vantage6Server
16
- from vantage6.algorithm.store.model.user import User
17
- from vantage6.backend.common.permission import RuleNeed
18
- from vantage6.backend.common.services_resources import BaseServicesResources
19
13
  from vantage6.algorithm.store.model.common.enums import (
20
14
  DefaultStorePolicies,
21
15
  )
22
16
  from vantage6.algorithm.store.model.policy import Policy
17
+ from vantage6.algorithm.store.model.rule import Operation
18
+ from vantage6.algorithm.store.model.user import User
19
+ from vantage6.algorithm.store.model.vantage6_server import Vantage6Server
20
+ from vantage6.backend.common.permission import RuleNeed
21
+ from vantage6.backend.common.services_resources import BaseServicesResources
22
+ from vantage6.common import logger_name
23
+ from vantage6.common.enum import AlgorithmViewPolicies, StorePolicies
23
24
 
24
25
  log = logging.getLogger(logger_name(__name__))
25
26
 
@@ -124,9 +125,68 @@ def request_validate_server_token(
124
125
  return None, None
125
126
 
126
127
 
127
- def _authenticate_with_server(*args, **kwargs):
128
+ def request_validate_node_token(
129
+ server_url: str,
130
+ ) -> tuple[Response, int] | tuple[None, None]:
131
+ """
132
+ Validate the token of a node.
133
+
134
+ Parameters
135
+ ----------
136
+ server_url : str
137
+ URL of the server to validate the token of.
138
+
139
+ Returns
140
+ -------
141
+ tuple[Response, int] | tuple[None, None]
142
+ Response object from the request, or None if the server could not be reached
128
143
  """
129
- Authenticate with a vantage6 server.
144
+ url = f"{server_url}/token/node/validate"
145
+ try:
146
+ return request_from_store_to_v6_server(url, method="post")
147
+ except requests.exceptions.ConnectionError:
148
+ return None, None
149
+
150
+
151
+ def _is_node_client_request() -> bool:
152
+ """
153
+ Check whether the request self-identifies as coming from a node.
154
+
155
+ This is only a routing hint - it decides which remote endpoint to validate the
156
+ token against (see `_authenticate_node_with_server`), not a trust decision by
157
+ itself. A request that sets this header with e.g. a user's token will simply fail
158
+ node validation at the server, since the server's node-validate endpoint only
159
+ accepts node tokens.
160
+
161
+ Returns
162
+ -------
163
+ bool
164
+ Whether the request identifies itself as coming from a node.
165
+ """
166
+ return request.headers.get("Client-Type") == "node"
167
+
168
+
169
+ def _authenticate_entity_with_server(
170
+ validate_token: Callable[[str], tuple[Response, int] | tuple[None, None]],
171
+ ) -> tuple[Response | dict, HTTPStatus]:
172
+ """
173
+ Authenticate the caller (a user or a node) with its own vantage6 server.
174
+
175
+ Shared by `_authenticate_with_server` and `_authenticate_node_with_server`, which
176
+ differ only in which server endpoint confirms the token.
177
+
178
+ Parameters
179
+ ----------
180
+ validate_token : Callable[[str], tuple[Response, int] | tuple[None, None]]
181
+ Function that asks the server at the given URL whether the caller's token
182
+ (from the `Authorization` header) is valid - `request_validate_server_token`
183
+ for users, `request_validate_node_token` for nodes.
184
+
185
+ Returns
186
+ -------
187
+ tuple[Response | dict, HTTPStatus]
188
+ The server's response and 200 if the token is valid, or an error message and
189
+ an appropriate status code otherwise.
130
190
  """
131
191
  msg = "Missing Server-Url header"
132
192
  if not request.headers.get("Server-Url"):
@@ -149,7 +209,7 @@ def _authenticate_with_server(*args, **kwargs):
149
209
  return {"msg": msg}, HTTPStatus.FORBIDDEN
150
210
 
151
211
  # check if token is valid
152
- response, status_code = request_validate_server_token(server_url)
212
+ response, status_code = validate_token(server_url)
153
213
  if response is None or status_code == HTTPStatus.NOT_FOUND:
154
214
  msg = "Could not connect to the vantage6 server. Please check the server URL."
155
215
  log.warning(msg)
@@ -167,6 +227,25 @@ def _authenticate_with_server(*args, **kwargs):
167
227
  return response, HTTPStatus.OK
168
228
 
169
229
 
230
+ def _authenticate_with_server(*args, **kwargs) -> tuple[Response | dict, HTTPStatus]:
231
+ """
232
+ Authenticate a user with its own vantage6 server.
233
+ """
234
+ return _authenticate_entity_with_server(request_validate_server_token)
235
+
236
+
237
+ def _authenticate_node_with_server() -> tuple[Response | dict, HTTPStatus]:
238
+ """
239
+ Authenticate a node with its own vantage6 server.
240
+
241
+ Unlike `_authenticate_with_server`, a successful result here never looks the node
242
+ up as a store `User` - nodes are recognized purely by their own server confirming
243
+ the token is valid, for the small number of endpoints that explicitly opt in to
244
+ accepting them (see `with_permission_to_view_algorithms`'s `allow_node` option).
245
+ """
246
+ return _authenticate_entity_with_server(request_validate_node_token)
247
+
248
+
170
249
  def _authorize_user(
171
250
  auth_response: requests.Response, resource: str, operation: Operation
172
251
  ) -> tuple | None:
@@ -295,10 +374,17 @@ def with_permission(resource: str, operation: Operation) -> callable:
295
374
  return protection_decorator
296
375
 
297
376
 
298
- def with_permission_to_view_algorithms() -> callable:
377
+ def with_permission_to_view_algorithms(allow_node: bool = False) -> callable:
299
378
  """
300
379
  Decorator to verify that the user has as a permission on a resource.
301
380
 
381
+ Parameters
382
+ ----------
383
+ allow_node : bool, optional
384
+ If True, also allow requests that self-identify as coming from a node (see
385
+ `_is_node_client_request`) to view approved algorithms, regardless of the
386
+ store's `algorithm_view` policy.
387
+
302
388
  Returns
303
389
  -------
304
390
  callable
@@ -309,12 +395,6 @@ def with_permission_to_view_algorithms() -> callable:
309
395
  def protection_decorator(fn):
310
396
  @wraps(fn)
311
397
  def decorator(self, *args, **kwargs):
312
- policies = Policy.get_as_dict()
313
- # check if everyone has permission to view algorithms
314
- algorithm_view_policy = policies.get(
315
- StorePolicies.ALGORITHM_VIEW, DefaultStorePolicies.ALGORITHM_VIEW.value
316
- )
317
-
318
398
  # check if user is trying to view algorithms that are not approved by review
319
399
  # or have been invalidated - these algorithms always require authentication
320
400
  # even when algorithms are open to all
@@ -326,6 +406,26 @@ def with_permission_to_view_algorithms() -> callable:
326
406
  or request_args.get("invalidated")
327
407
  )
328
408
 
409
+ # a node re-verifying an image against this store's catalog is always
410
+ # allowed to see approved algorithms, independent of the store's
411
+ # algorithm_view policy - it is never allowed to see non-approved ones.
412
+ # Unlike the user path below, a node that identifies itself here but
413
+ # fails authentication is rejected outright rather than falling through
414
+ # to the user flow, since a node's token would never pass user
415
+ # authentication either.
416
+ if allow_node and request_approved and _is_node_client_request():
417
+ response, status = _authenticate_node_with_server()
418
+ if status != HTTPStatus.OK:
419
+ return response, status
420
+ g.node = True
421
+ return fn(self, *args, **kwargs)
422
+
423
+ policies = Policy.get_as_dict()
424
+ # check if everyone has permission to view algorithms
425
+ algorithm_view_policy = policies.get(
426
+ StorePolicies.ALGORITHM_VIEW, DefaultStorePolicies.ALGORITHM_VIEW.value
427
+ )
428
+
329
429
  # TODO v5+ remove this deprecated policy "algorithms_open"
330
430
  anyone_can_view = policies.get("algorithms_open", False)
331
431
  if (
@@ -181,7 +181,7 @@ class AlgorithmBaseResource(AlgorithmStoreResources):
181
181
  class Algorithms(AlgorithmBaseResource):
182
182
  """Resource for /algorithm"""
183
183
 
184
- @with_permission_to_view_algorithms()
184
+ @with_permission_to_view_algorithms(allow_node=True)
185
185
  def get(self):
186
186
  """List algorithms
187
187
  ---
@@ -192,6 +192,12 @@ class Algorithms(AlgorithmBaseResource):
192
192
  algorithms, set the 'awaiting_reviewer_assignment', 'under_review' or
193
193
  'invalidated' parameter to True.
194
194
 
195
+ Nodes are always allowed to list approved algorithms here (e.g. filtered by
196
+ 'image'), regardless of this store's algorithm_view policy - this lets a node
197
+ independently verify that an image is a registered, approved algorithm for
198
+ its own allowed_algorithm_stores policy. Nodes cannot use the non-approved
199
+ filters above.
200
+
195
201
  parameters:
196
202
  - in: query
197
203
  name: name
@@ -971,6 +977,12 @@ class Algorithm(AlgorithmBaseResource):
971
977
  if not algorithm:
972
978
  return {"msg": "Algorithm not found"}, HTTPStatus.NOT_FOUND
973
979
 
980
+ # only the algorithm's own developer may edit it
981
+ if algorithm.developer_id != g.user.id:
982
+ return {
983
+ "msg": "You can only edit algorithms you submitted yourself."
984
+ }, HTTPStatus.FORBIDDEN
985
+
974
986
  data = request.get_json()
975
987
 
976
988
  # validate the request body
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: vantage6_algorithm_store
3
- Version: 4.15.1
3
+ Version: 4.15.2
4
4
  Summary: Vantage6 algorithm store
5
5
  Home-page: https://github.com/vantage6/vantage6
6
6
  Requires-Python: >=3.10
@@ -17,8 +17,8 @@ Requires-Dist: requests==2.33.1
17
17
  Requires-Dist: schema==0.7.5
18
18
  Requires-Dist: SQLAlchemy==1.4.46
19
19
  Requires-Dist: werkzeug==3.1.8
20
- Requires-Dist: vantage6==4.15.1
21
- Requires-Dist: vantage6-common==4.15.1
20
+ Requires-Dist: vantage6==4.15.2
21
+ Requires-Dist: vantage6-common==4.15.2
22
22
  Provides-Extra: dev
23
23
  Requires-Dist: coverage==6.4.4; extra == "dev"
24
24
  Dynamic: description
@@ -10,8 +10,8 @@ requests==2.33.1
10
10
  schema==0.7.5
11
11
  SQLAlchemy==1.4.46
12
12
  werkzeug==3.1.8
13
- vantage6==4.15.1
14
- vantage6-common==4.15.1
13
+ vantage6==4.15.2
14
+ vantage6-common==4.15.2
15
15
 
16
16
  [dev]
17
17
  coverage==6.4.4