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.
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/PKG-INFO +3 -3
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_algorithm.py +102 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/_version.py +1 -1
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/__init__.py +121 -21
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/algorithm.py +13 -1
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/PKG-INFO +3 -3
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/requires.txt +2 -2
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/setup.cfg +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/setup.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/__init__.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/base/__init__.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/base/unittest_base.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/__init__.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_policy.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_review.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_role.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_rule.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_user.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_v6server.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/resources/test_version.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/test___init__.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/test_permission.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/__build__ +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/__init__.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/db.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/default_roles.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/globals.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/__init__.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/algorithm.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/allowed_argument_value.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/argument.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/base.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/common/enums.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/common/ui_visualization_schemas.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/database.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/function.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/permission.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/policy.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/review.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/role.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/role_rule_association.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/rule.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/ui_visualization.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/user.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/model/vantage6_server.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/permission.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/policy.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/review.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/role.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/rule.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/schema/input_schema.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/schema/output_schema.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/user.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/vantage6_server.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/resource/version.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/wsgi.py +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/SOURCES.txt +0 -0
- {vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6_algorithm_store.egg-info/dependency_links.txt +0 -0
- {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.
|
|
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.
|
|
21
|
-
Requires-Dist: vantage6-common==4.15.
|
|
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,
|
|
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
|
|
2
|
+
import os
|
|
3
|
+
from collections.abc import Callable
|
|
4
4
|
from functools import wraps
|
|
5
5
|
from http import HTTPStatus
|
|
6
|
-
|
|
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
|
|
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
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
21
|
-
Requires-Dist: vantage6-common==4.15.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/base/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/test___init__.py
RENAMED
|
File without changes
|
{vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/tests_store/test_permission.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/db.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vantage6_algorithm_store-4.15.1 → vantage6_algorithm_store-4.15.2}/vantage6/algorithm/store/wsgi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|