diff --git a/lta/rest_server.py b/lta/rest_server.py index 6ec1ea1..3be5062 100644 --- a/lta/rest_server.py +++ b/lta/rest_server.py @@ -18,7 +18,7 @@ import pymongo from pymongo import MongoClient from rest_tools.utils.json_util import json_decode -from rest_tools.server import RestHandler, RestHandlerSetup, RestServer +from rest_tools.server import RestHandler, RestHandlerSetup, RestServer, ArgumentHandler, ArgumentSource from rest_tools.server.decorators import keycloak_role_auth import tornado.web from wipac_dev_tools import from_environment @@ -28,6 +28,7 @@ EXPECTED_CONFIG = { 'LOG_LEVEL': 'DEBUG', + 'CI_TEST': 'FALSE', 'LTA_AUTH_AUDIENCE': 'long-term-archive', 'LTA_AUTH_OPENID_URL': '', 'LTA_MAX_BODY_SIZE': '16777216', # 16 MB is the limit of MongoDB documents @@ -63,23 +64,7 @@ # ----------------------------------------------------------------------------- -if bool(os.environ.get('CI_TEST_ENV', False)): - def lta_auth(**_auth: Any) -> Callable[..., Any]: - def make_wrapper(method: Callable[..., Any]) -> Any: - async def wrapper(self: Any, *args: Any, **kwargs: Any) -> Any: - # warn the user about authentication being disabled in testing - logging.warning("TESTING: auth disabled") - # ensure that the code provided at least one required authentication role - # note: if the handler doesn't need auth; don't apply an auth decorator! - roles = _auth.get('roles', []) - if not roles: - raise Exception("No roles provided to lta_auth decorator!") - # go ahead and run the handler - return await method(self, *args, **kwargs) - return wrapper - return make_wrapper -else: - lta_auth = keycloak_role_auth +lta_auth = keycloak_role_auth # ----------------------------------------------------------------------------- @@ -403,8 +388,16 @@ class MetadataActionsBulkCreateHandler(BaseLTAHandler): async def post(self) -> None: """Handle POST /Metadata/actions/bulk_create.""" request_counter.labels(method='POST', route='/Metadata/actions/bulk_create').inc() - bundle_uuid = self.get_argument("bundle_uuid", type=str) - files = self.get_argument("files", type=list, forbiddens=[[]]) + argo = ArgumentHandler(ArgumentSource.JSON_BODY_ARGUMENTS, self) + argo.add_argument('bundle_uuid', type=str) + argo.add_argument("files", type=list) + args = argo.parse_args() + bundle_uuid = args.bundle_uuid + files = args.files + if not bundle_uuid: + raise tornado.web.HTTPError(400, reason='bundle_uuid must not be empty') + if not files: + raise tornado.web.HTTPError(400, reason='files must not be empty') documents = [] for file_catalog_uuid in files: @@ -437,7 +430,12 @@ class MetadataActionsBulkDeleteHandler(BaseLTAHandler): async def post(self) -> None: """Handle POST /Metadata/actions/bulk_delete.""" request_counter.labels(method='POST', route='/Metadata/actions/bulk_delete').inc() - metadata = self.get_argument("metadata", type=list, forbiddens=[[]]) + argo = ArgumentHandler(ArgumentSource.JSON_BODY_ARGUMENTS, self) + argo.add_argument("metadata", type=list) + args = argo.parse_args() + metadata = args.metadata + if not metadata: + raise tornado.web.HTTPError(400, reason='metadata must not be empty') count = 0 slice_index = 0 @@ -491,7 +489,9 @@ async def get(self) -> None: async def delete(self) -> None: """Handle DELETE /Metadata?bundle_uuid={uuid}.""" request_counter.labels(method='DELETE', route='/Metadata?bundle_uuid={uuid}').inc() - bundle_uuid = self.get_argument("bundle_uuid", type=str) + bundle_uuid = self.get_argument("bundle_uuid", None) + if not bundle_uuid: + raise tornado.web.HTTPError(400, reason='bundle_uuid must not be empty') query = {"bundle_uuid": bundle_uuid} logging.debug(f"MONGO-START: db.Metadata.delete_many(filter={query})") await self.db.Metadata.delete_many(filter=query) @@ -662,7 +662,7 @@ class TransferRequestActionsPopHandler(BaseLTAHandler): async def post(self) -> None: """Handle POST /TransferRequests/actions/pop.""" request_counter.labels(method='POST', route='/TransferRequests/actions/pop').inc() - source = self.get_argument("source", type=str) + source = self.get_argument("source") pop_body = json_decode(self.request.body) if 'claimant' not in pop_body: response_counter.labels(method='POST', response='400', route='/TransferRequests/actions/pop').inc() @@ -766,11 +766,18 @@ def start(debug: bool = False) -> RestServer: else: logging.info(f"{name} = NOT SPECIFIED") - args = RestHandlerSetup({ # type: ignore - "auth": { + if config["CI_TEST"] == "TRUE": + auth = { + "secret": "secret", + } + else: + auth = { "audience": config["LTA_AUTH_AUDIENCE"], - "openid_url": config["LTA_AUTH_OPENID_URL"], - }, + "openid_url": config["LTA_AUTH_OPENID_URL"] + } + + args = RestHandlerSetup({ # type: ignore + "auth": auth, "debug": debug }) # configure access to MongoDB as a backing store diff --git a/setup.cfg b/setup.cfg index 272e035..ff3951a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [wipac:cicd_setup_builder] -python_min = 3.8 +python_min = 3.9 [metadata] # generated by wipac:cicd_setup_builder: version, keywords version = attr: lta.__version__ @@ -29,7 +29,7 @@ install_requires = wipac-dev-tools wipac-rest-tools wipac-telemetry -python_requires = >=3.8, <3.13 +python_requires = >=3.9, <3.13 packages = find: [options.extras_require] diff --git a/tests/test_rest_server.py b/tests/test_rest_server.py index 4865a63..fc630af 100644 --- a/tests/test_rest_server.py +++ b/tests/test_rest_server.py @@ -2,6 +2,7 @@ """Unit tests for lta/rest_server.py.""" import asyncio +import logging import os import socket import tracemalloc @@ -30,8 +31,6 @@ CONFIG = { "LOG_LEVEL": "DEBUG", - "LTA_AUTH_AUDIENCE": "lta", - "LTA_AUTH_OPENID_URL": "localhost:12345", 'LTA_MONGODB_AUTH_USER': '', 'LTA_MONGODB_AUTH_PASS': '', 'LTA_MONGODB_DATABASE_NAME': 'lta', @@ -80,8 +79,7 @@ def port() -> int: async def rest(monkeypatch: MonkeyPatch, port: int) -> AsyncGenerator[RestClientFactory, None]: """Provide RestClient as a test fixture.""" # setup_function - monkeypatch.setenv("LTA_AUTH_AUDIENCE", CONFIG["LTA_AUTH_AUDIENCE"]) - monkeypatch.setenv("LTA_AUTH_OPENID_URL", CONFIG["LTA_AUTH_OPENID_URL"]) + monkeypatch.setenv("CI_TEST", "TRUE") monkeypatch.setenv("LTA_MONGODB_DATABASE_NAME", CONFIG['LTA_MONGODB_DATABASE_NAME']) monkeypatch.setenv("LTA_REST_PORT", str(port)) monkeypatch.setenv("LTA_SITE_CONFIG", "examples/site.json") @@ -96,10 +94,15 @@ def client(role: str = "admin", timeout: float = 0.5) -> RestClient: # Sauron forged in secret a master Token, to control all others. And # into this Token he poured his cruelty, his malice and his will to # dominate all life. One Token to rule them all. - auth = Auth("secret", issuer="LTA") # type: ignore[no-untyped-call] + auth = Auth("secret") # type: ignore[no-untyped-call] token_data: Dict[str, Any] = { - # TODO: fill in some token stuff here + "resource_access": { + "long-term-archive": { + "roles": [role] + } + } } + logging.info("setting role to %s", role) token = auth.create_token(subject="lta", # type: ignore[no-untyped-call] expiration=300, type="temp", @@ -1047,31 +1050,25 @@ async def test_metadata_actions_bulk_create_errors(rest: RestClientFactory) -> N with pytest.raises(HTTPError) as e: await r.request('POST', '/Metadata/actions/bulk_create', request) assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`bundle_uuid`: (MissingArgumentError) required argument is missing" + assert "bundle_uuid" in e.value.response.json()["error"] - request = {'bundle_uuid': []} + request = {'bundle_uuid': '', 'files': ["foo"]} with pytest.raises(HTTPError) as e: await r.request('POST', '/Metadata/actions/bulk_create', request) assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`files`: (MissingArgumentError) required argument is missing" + assert "bundle_uuid" in e.value.response.json()["error"] request = {'bundle_uuid': "992ae5e1-017c-4a95-b552-bd385020ec27"} with pytest.raises(HTTPError) as e: await r.request('POST', '/Metadata/actions/bulk_create', request) assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`files`: (MissingArgumentError) required argument is missing" - - request = {'bundle_uuid': "992ae5e1-017c-4a95-b552-bd385020ec27", "files": {}} - with pytest.raises(HTTPError) as e: - await r.request('POST', '/Metadata/actions/bulk_create', request) - assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`files`: (ValueError) [] is forbidden ([[]])" + assert "files" in e.value.response.json()["error"] request = {'bundle_uuid': "992ae5e1-017c-4a95-b552-bd385020ec27", "files": []} with pytest.raises(HTTPError) as e: await r.request('POST', '/Metadata/actions/bulk_create', request) assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`files`: (ValueError) [] is forbidden ([[]])" + assert "files" in e.value.response.json()["error"] r.close() @@ -1084,19 +1081,19 @@ async def test_metadata_actions_bulk_delete_errors(rest: RestClientFactory) -> N with pytest.raises(HTTPError) as e: await r.request('POST', '/Metadata/actions/bulk_delete', request) assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`metadata`: (MissingArgumentError) required argument is missing" + assert "metadata" in e.value.response.json()["error"] request = {'metadata': ''} with pytest.raises(HTTPError) as e: await r.request('POST', '/Metadata/actions/bulk_delete', request) assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`metadata`: (ValueError) [] is forbidden ([[]])" + assert "metadata" in e.value.response.json()["error"] request = {'metadata': []} with pytest.raises(HTTPError) as e: await r.request('POST', '/Metadata/actions/bulk_delete', request) assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`metadata`: (ValueError) [] is forbidden ([[]])" + assert "metadata" in e.value.response.json()["error"] r.close() @@ -1108,7 +1105,7 @@ async def test_metadata_delete_errors(rest: RestClientFactory) -> None: with pytest.raises(HTTPError) as e: await r.request('DELETE', '/Metadata') assert e.value.response.status_code == 400 - assert e.value.response.json()["error"] == "`bundle_uuid`: (MissingArgumentError) required argument is missing" + assert "bundle_uuid" in e.value.response.json()["error"] r.close()