diff --git a/monitoring/uss_qualifier/resources/astm/f3548/v21/planning_area.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/planning_area.py index 73bf102364..eade2fae25 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/planning_area.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/planning_area.py @@ -1,10 +1,19 @@ import datetime -from typing import List, Dict, Any, Optional, Self +from typing import List, Optional -from implicitdict import ImplicitDict, StringBasedDateTime -from uas_standards.astm.f3548.v21.api import Volume4D +from implicitdict import ImplicitDict +from uas_standards.astm.f3548.v21.api import ( + EntityOVN, + OperationalIntentState, + UssBaseURL, + EntityID, + PutOperationalIntentReferenceParameters, + ImplicitSubscriptionParameters, +) -from monitoring.monitorlib.geo import LatLngPoint, make_latlng_rect, Volume3D, Polygon +from monitoring.monitorlib.geo import make_latlng_rect, Volume3D +from monitoring.monitorlib.geotemporal import Volume4D +from monitoring.monitorlib.temporal import Time from monitoring.uss_qualifier.resources.astm.f3548.v21.subscription_params import ( SubscriptionParams, ) @@ -52,6 +61,46 @@ def get_new_subscription_params( notify_for_constraints=notify_for_constraints, ) + def get_new_operational_intent_ref_params( + self, + key: List[EntityOVN], + state: OperationalIntentState, + uss_base_url: UssBaseURL, + time_start: datetime.datetime, + time_end: datetime.datetime, + subscription_id: Optional[EntityID], + implicit_sub_base_url: Optional[UssBaseURL] = None, + implicit_sub_for_constraints: Optional[bool] = None, + ) -> PutOperationalIntentReferenceParameters: + """ + Build a PutOperationalIntentReferenceParameters object that can be used against the DSS OIR API. + + The extents contained in these parameters contain a single 4DVolume, which may not be entirely realistic, + but is sufficient in situations where the content of the OIR is irrelevant as long as it is valid, such + as for testing authentication or parameter validation. + + Note that this method allows building inconsistent parameters: + """ + return PutOperationalIntentReferenceParameters( + extents=[ + Volume4D( + volume=self.volume, + time_start=Time(time_start), + time_end=Time(time_end), + ).to_f3548v21() + ], + key=key, + state=state, + uss_base_url=uss_base_url, + subscription_id=subscription_id, + new_subscription=ImplicitSubscriptionParameters( + uss_base_url=implicit_sub_base_url, + notify_for_constraints=implicit_sub_for_constraints, + ) + if implicit_sub_base_url + else None, + ) + class PlanningAreaResource(Resource[PlanningAreaSpecification]): specification: PlanningAreaSpecification diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py index 1426afb9e3..6246292e11 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/__init__.py @@ -1,3 +1,2 @@ from .subscription_validation import SubscriptionValidation from .subscription_simple import SubscriptionSimple -from .authentication_validation import AuthenticationValidation diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/__init__.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/__init__.py new file mode 100644 index 0000000000..8871d77868 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/__init__.py @@ -0,0 +1 @@ +from .authentication_validation import AuthenticationValidation diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md similarity index 75% rename from monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication_validation.md rename to monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md index 079ae108ad..4d5c528f34 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication_validation.md +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md @@ -10,19 +10,19 @@ Note that this does not cover authorization. ### dss -[`DSSInstanceResource`](../../../../resources/astm/f3548/v21/dss.py) to be tested in this scenario. +[`DSSInstanceResource`](../../../../../resources/astm/f3548/v21/dss.py) to be tested in this scenario. ### id_generator -[`IDGeneratorResource`](../../../../resources/interuss/id_generator.py) providing the Subscription ID for this scenario. +[`IDGeneratorResource`](../../../../../resources/interuss/id_generator.py) providing the Subscription ID for this scenario. ### planning_area -[`PlanningAreaResource`](../../../../resources/astm/f3548/v21/planning_area.py) describes the 3D volume in which entities will be created. +[`PlanningAreaResource`](../../../../../resources/astm/f3548/v21/planning_area.py) describes the 3D volume in which entities will be created. ## Setup test case -### [Ensure clean workspace test step](clean_workspace.md) +### [Ensure clean workspace test step](../clean_workspace.md) This step ensures that no entity with the known test IDs exists in the DSS. @@ -34,131 +34,131 @@ This test case ensures that the DSS properly authenticates requests to all its e #### 🛑 Unauthorized requests return the proper error message body check -If the DSS under test does not return a proper error message body when an unauthorized request is received, it fails to properly implement the OpenAPI specification that is part of **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. +If the DSS under test does not return a proper error message body when an unauthorized request is received, it fails to properly implement the OpenAPI specification that is part of **[astm.f3548.v21.DSS0005,5](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Create subscription with missing credentials check -If the DSS under test allows the creation of a subscription without any credentials being presented, it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +If the DSS under test allows the creation of a subscription without any credentials being presented, it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Create subscription with invalid credentials check If the DSS under test allows the creation of a subscription with credentials that are well-formed but invalid, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Create subscription with missing scope check If the DSS under test allows the creation of a subscription with valid credentials but a missing scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Create subscription with incorrect scope check If the DSS under test allows the creation of a subscription with valid credentials but an incorrect scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Create subscription with valid credentials check If the DSS does not allow the creation of a subscription when valid credentials are presented, -it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Get subscription with missing credentials check -If the DSS under test allows the fetching of a subscription without any credentials being presented, it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +If the DSS under test allows the fetching of a subscription without any credentials being presented, it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Get subscription with invalid credentials check If the DSS under test allows the fetching of a subscription with credentials that are well-formed but invalid, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Get subscription with missing scope check If the DSS under test allows the fetching of a subscription with valid credentials but a missing scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Get subscription with incorrect scope check If the DSS under test allows the fetching of a subscription with valid credentials but an incorrect scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Get subscription with valid credentials check If the DSS does not allow fetching a subscription when valid credentials are presented, -it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Mutate subscription with missing credentials check If the DSS under test allows the mutation of a subscription without any credentials being presented, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Mutate subscription with invalid credentials check If the DSS under test allows the mutation of a subscription with credentials that are well-formed but invalid, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Mutate subscription with missing scope check If the DSS under test allows the mutation of a subscription with valid credentials but a missing scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Mutate subscription with incorrect scope check If the DSS under test allows the mutation of a subscription with valid credentials but an incorrect scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Mutate subscription with valid credentials check If the DSS does not allow the mutation of a subscription when valid credentials are presented, -it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Delete subscription with missing credentials check If the DSS under test allows the deletion of a subscription without any credentials being presented, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Delete subscription with invalid credentials check If the DSS under test allows the deletion of a subscription with credentials that are well-formed but invalid, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Delete subscription with missing scope check If the DSS under test allows the deletion of a subscription with valid credentials but a missing scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Delete subscription with incorrect scope check If the DSS under test allows the deletion of a subscription with valid credentials but an incorrect scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Delete subscription with valid credentials check If the DSS does not allow the deletion of a subscription when valid credentials are presented, -it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Search subscriptions with missing credentials check If the DSS under test allows searching for subscriptions without any credentials being presented, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Search subscriptions with invalid credentials check If the DSS under test allows searching for subscriptions with credentials that are well-formed but invalid, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Search subscriptions with missing scope check If the DSS under test allows searching for subscriptions with valid credentials but a missing scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Search subscriptions with incorrect scope check If the DSS under test allows searching for subscriptions with valid credentials but an incorrect scope, -it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0210,A2-7-2,7](../../../../../requirements/astm/f3548/v21.md)**. #### 🛑 Search subscriptions with valid credentials check If the DSS does not allow searching for subscriptions when valid credentials are presented, -it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../requirements/astm/f3548/v21.md)**. +it is in violation of **[astm.f3548.v21.DSS0005,5](../../../../../requirements/astm/f3548/v21.md)**. -## [Cleanup](./clean_workspace.md) +## [Cleanup](../clean_workspace.md) The cleanup phase of this test scenario removes the subscription with the known test ID if it has not been removed before. diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py new file mode 100644 index 0000000000..c6affd219c --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py @@ -0,0 +1,151 @@ +from datetime import datetime, timedelta + +from uas_standards.astm.f3548.v21.constants import ( + Scope, +) + +from monitoring.monitorlib import fetch +from monitoring.monitorlib.auth import InvalidTokenSignatureAuth +from monitoring.monitorlib.fetch import QueryError +from monitoring.monitorlib.geotemporal import Volume4D +from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.prober.infrastructure import register_resource_type +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstanceResource +from monitoring.uss_qualifier.resources.astm.f3548.v21.planning_area import ( + PlanningAreaResource, + PlanningAreaSpecification, +) +from monitoring.uss_qualifier.resources.astm.f3548.v21.subscription_params import ( + SubscriptionParams, +) +from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource +from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments +from monitoring.uss_qualifier.scenarios.astm.utm.dss.authentication.generic import ( + GenericAuthValidator, +) +from monitoring.uss_qualifier.scenarios.astm.utm.dss.authentication.sub_api_validator import ( + SubscriptionAuthValidator, +) +from monitoring.uss_qualifier.scenarios.scenario import ( + TestScenario, +) +from monitoring.uss_qualifier.suites.suite import ExecutionContext + + +class AuthenticationValidation(TestScenario): + """ + A scenario that verifies that the DSS properly authenticates requests to all its endpoints. + + This scenario does not (yet) cover anything related to authorization: this first version + is intended to cover DSS0210,A2-7-2,7 + """ + + SUB_TYPE = register_resource_type( + 380, "Subscription, Operational Entity Id, Constraint" + ) + + # Reuse the same ID for every type of entity. + # As we are testing serially and cleaning up after each test, this should be fine. + _test_id: str + """Base identifier for the entities that will be created""" + + def __init__( + self, + dss: DSSInstanceResource, + id_generator: IDGeneratorResource, + planning_area: PlanningAreaResource, + ): + """ + Args: + dss: dss to test + id_generator: will let us generate specific identifiers + planning_area: An Area to use for the tests. It should be an area for which the DSS is responsible, + but has no other requirements. + """ + super().__init__() + scopes = {Scope.StrategicCoordination: "create and delete subscriptions"} + # This is an UTMClientSession + self._dss = dss.get_instance(scopes) + self._pid = [self._dss.participant_id] + self._test_id = id_generator.id_factory.make_id(self.SUB_TYPE) + self._planning_area = planning_area.specification + + # Build a ready-to-use 4D volume with no specified time for searching + # the currently active subscriptions + self._planning_area_volume4d = Volume4D( + volume=self._planning_area.volume, + ) + + self._sub_params = self._planning_area.get_new_subscription_params( + subscription_id=self._test_id, + # Set this slightly in the past: we will update the subscriptions + # to a later value that still needs to be roughly 'now' without getting into the future + start_time=datetime.now().astimezone() - timedelta(seconds=10), + duration=timedelta(minutes=20), + # This is a planning area without constraint processing + notify_for_op_intents=True, + notify_for_constraints=False, + ) + + # Session that won't provide a token at all + self._no_auth_session = UTMClientSession(self._dss.base_url, auth_adapter=None) + + # Session that should provide a well-formed token with a wrong signature + self._invalid_token_session = UTMClientSession( + self._dss.base_url, auth_adapter=InvalidTokenSignatureAuth() + ) + + generic_validator = GenericAuthValidator(self, self._dss) + + self._sub_validator = SubscriptionAuthValidator( + self, + generic_validator, + self._dss, + self._test_id, + self._planning_area, + self._planning_area_volume4d, + self._no_auth_session, + self._invalid_token_session, + ) + + def run(self, context: ExecutionContext): + self.begin_test_scenario(context) + self._setup_case() + self.begin_test_case("Endpoint authorization") + + self.begin_test_step("Subscription endpoints authentication") + self._sub_validator.verify_sub_endpoints_authentication() + self.end_test_step() + + self.end_test_case() + self.end_test_scenario() + + def _setup_case(self): + self.begin_test_case("Setup") + + self._ensure_clean_workspace_step() + + self.end_test_case() + + def _ensure_clean_workspace_step(self): + self.begin_test_step("Ensure clean workspace") + # Check for subscriptions that will collide with our IDs and drop them + self._ensure_test_entities_dont_exist() + # Drop any active remaining sub + self._ensure_no_active_subs_exist() + self.end_test_step() + + def _ensure_test_entities_dont_exist(self): + test_step_fragments.cleanup_sub(self, self._dss, self._test_id) + + def _ensure_no_active_subs_exist(self): + test_step_fragments.cleanup_active_subs( + self, + self._dss, + self._planning_area_volume4d, + ) + + def cleanup(self): + self.begin_cleanup() + self._ensure_test_entities_dont_exist() + self.end_cleanup() diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py new file mode 100644 index 0000000000..b687eb65d5 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py @@ -0,0 +1,99 @@ +from uas_standards.astm.f3548.v21.constants import Scope + +from monitoring.monitorlib import fetch +from monitoring.monitorlib.auth import InvalidTokenSignatureAuth +from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance +from monitoring.uss_qualifier.scenarios.scenario import TestScenario + + +class GenericAuthValidator: + """ + Utility class for common DSS authentication validation requirements. + """ + + def __init__( + self, + scenario: TestScenario, + dss: DSSInstance, + ): + self._scenario = scenario + self._authenticated_session = dss.client + self._invalid_token_session = UTMClientSession( + dss.base_url, auth_adapter=InvalidTokenSignatureAuth() + ) + self._no_auth_session = UTMClientSession(dss.base_url, auth_adapter=None) + + def query_no_auth(self, **query_kwargs) -> fetch.Query: + """Issue a query to the DSS without any credentials being passed""" + q = fetch.query_and_describe(client=self._no_auth_session, **query_kwargs) + self._scenario.record_query(q) + return q + + def query_invalid_token(self, **query_kwargs) -> fetch.Query: + """ + Issue a query to the DSS with an invalid token signature, but a valid token. + An appropriate scope is provided. + """ + q = fetch.query_and_describe( + client=self._invalid_token_session, + scope=Scope.StrategicCoordination, + **query_kwargs, + ) + self._scenario.record_query(q) + return q + + def query_missing_scope(self, **query_kwargs) -> fetch.Query: + """ + Issue a query to the DSS with a valid token, but omits specifying a scope. + """ + q = fetch.query_and_describe(client=self._authenticated_session, **query_kwargs) + self._scenario.record_query(q) + return q + + def query_wrong_scope(self, **query_kwargs) -> fetch.Query: + """ + Issue a query to the DSS with a valid token, but with a scope that is not allowed + to perform the operation. + """ + q = fetch.query_and_describe( + client=self._authenticated_session, + scope=Scope.AvailabilityArbitration, # TODO make these configurable via the constructor? + **query_kwargs, + ) + self._scenario.record_query(q) + return q + + def query_valid_auth(self, **query_kwargs) -> fetch.Query: + """ + Issue a query to the DSS with valid credentials. + """ + q = fetch.query_and_describe( + client=self._authenticated_session, + scope=Scope.StrategicCoordination, + # TODO make these configurable via the constructor? 'valid' may be different for certain endpoints + **query_kwargs, + ) + self._scenario.record_query(q) + return q + + def verify_4xx_response(self, q: fetch.Query): + """Verifies that the passed query response's body is a valid ErrorResponse: + it is either empty or contains a single 'message' field, as per the OpenAPI spec. + + Note that 409 responses to Operational Intent Reference mutations will contain more fields, + these are not handled here. + """ + with self._scenario.check( + "Unauthorized requests return the proper error message body" + ) as check: + if len(q.response.json) == 0: + return + elif len(q.response.json) == 1 and "message" in q.response.json: + return + else: + check.record_failed( + summary="Unexpected error response body", + details=f"Response body for {q.request.method} query to {q.request.url} should be empty or contain a single 'message' field. Was: {q.response.json}", + query_timestamps=[q.request.timestamp], + ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py similarity index 51% rename from monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication_validation.py rename to monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py index 140d511a20..7712f991fc 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py @@ -5,85 +5,47 @@ OperationID, QuerySubscriptionParameters, ) -from uas_standards.astm.f3548.v21.constants import ( - Scope, -) from monitoring.monitorlib import fetch -from monitoring.monitorlib.auth import InvalidTokenSignatureAuth from monitoring.monitorlib.fetch import QueryType from monitoring.monitorlib.geotemporal import Volume4D from monitoring.monitorlib.infrastructure import UTMClientSession from monitoring.monitorlib.mutate import scd as mutate from monitoring.monitorlib.mutate.scd import MutatedSubscription -from monitoring.prober.infrastructure import register_resource_type -from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstanceResource +from monitoring.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance from monitoring.uss_qualifier.resources.astm.f3548.v21.planning_area import ( - PlanningAreaResource, PlanningAreaSpecification, ) -from monitoring.uss_qualifier.resources.astm.f3548.v21.subscription_params import ( - SubscriptionParams, -) -from monitoring.uss_qualifier.resources.interuss.id_generator import IDGeneratorResource -from monitoring.uss_qualifier.scenarios.astm.utm.dss import test_step_fragments -from monitoring.uss_qualifier.scenarios.scenario import ( - TestScenario, - PendingCheck, +from monitoring.uss_qualifier.scenarios.astm.utm.dss.authentication.generic import ( + GenericAuthValidator, ) -from monitoring.uss_qualifier.suites.suite import ExecutionContext +from monitoring.uss_qualifier.scenarios.scenario import TestScenario, PendingCheck TIME_TOLERANCE_SEC = 1 -class AuthenticationValidation(TestScenario): - """ - A scenario that verifies that the DSS properly authenticates requests to all its endpoints. - - This scenario does not (yet) cover anything related to authorization: this first version - is intended to cover DSS0210,A2-7-2,7 - """ - - SUB_TYPE = register_resource_type(380, "Subscription") - - _sub_id: str - """Base identifier for the subscriptions that will be created""" - - _planning_area: PlanningAreaSpecification - - _planning_area_volume4d: Volume4D - - _sub_params: SubscriptionParams - +class SubscriptionAuthValidator: def __init__( self, - dss: DSSInstanceResource, - id_generator: IDGeneratorResource, - planning_area: PlanningAreaResource, + scenario: TestScenario, + generic_validator: GenericAuthValidator, + dss: DSSInstance, + test_id: str, + planning_area: PlanningAreaSpecification, + planning_area_volume4d: Volume4D, + no_auth_session: UTMClientSession, + invalid_token_session: UTMClientSession, ): - """ - Args: - dss: dss to test - id_generator: will let us generate specific identifiers - planning_area: An Area to use for the tests. It should be an area for which the DSS is responsible, - but has no other requirements. - """ - super().__init__() - scopes = {Scope.StrategicCoordination: "create and delete subscriptions"} - # This is an UTMClientSession - self._dss = dss.get_instance(scopes) - self._pid = [self._dss.participant_id] - self._sub_id = id_generator.id_factory.make_id(self.SUB_TYPE) - self._planning_area = planning_area.specification - - # Build a ready-to-use 4D volume with no specified time for searching - # the currently active subscriptions - self._planning_area_volume4d = Volume4D( - volume=self._planning_area.volume, - ) + self._scenario = scenario + self._gen_val = generic_validator + self._dss = dss + self._pid = dss.participant_id + self._test_id = test_id + self._planning_area = planning_area + self._planning_area_volume4d = planning_area_volume4d self._sub_params = self._planning_area.get_new_subscription_params( - subscription_id=self._sub_id, # subscription ID will need to be overwritten + subscription_id=self._test_id, # Set this slightly in the past: we will update the subscriptions # to a later value that still needs to be roughly 'now' without getting into the future start_time=datetime.now().astimezone() - timedelta(seconds=10), @@ -93,77 +55,15 @@ def __init__( notify_for_constraints=False, ) - # Session that won't provide a token at all - self._no_auth_session = UTMClientSession(self._dss.base_url, auth_adapter=None) - - # Session that should provide a well-formed token with a wrong signature - self._invalid_token_session = UTMClientSession( - self._dss.base_url, auth_adapter=InvalidTokenSignatureAuth() - ) - - def run(self, context: ExecutionContext): - self.begin_test_scenario(context) - self._setup_case() - self.begin_test_case("Endpoint authorization") - - self.begin_test_step("Subscription endpoints authentication") - self._verify_subscription_endpoints_authentication() - self.end_test_step() - - # TODO consider adding test cases for: - # - valid credentials without the required scopes - self.end_test_case() - self.end_test_scenario() - - def _setup_case(self): - self.begin_test_case("Setup") - - self._ensure_clean_workspace_step() - - self.end_test_case() - - def _ensure_clean_workspace_step(self): - self.begin_test_step("Ensure clean workspace") - # Start by dropping any active sub - self._ensure_no_active_subs_exist() - # Check for subscriptions that will collide with our IDs and drop them - self._ensure_test_sub_ids_do_not_exist() - self.end_test_step() - - def _ensure_test_sub_ids_do_not_exist(self): - test_step_fragments.cleanup_sub(self, self._dss, self._sub_id) - - def _ensure_no_active_subs_exist(self): - test_step_fragments.cleanup_active_subs( - self, - self._dss, - self._planning_area_volume4d, - ) - - def _verify_error_response(self, q: fetch.Query): - """Verifies that the passed query response's body is a valid ErrorResponse: - it is either empty or contains a single 'message' field, as per the OpenAPI spec. - """ - with self.check( - "Unauthorized requests return the proper error message body" - ) as check: - if len(q.response.json) == 0: - return - elif len(q.response.json) == 1 and "message" in q.response.json: - return - else: - check.record_failed( - summary="Unexpected error response body", - details=f"Response body for {q.request.method} query to {q.request.url} should be empty or contain a single 'message' field. Was: {q.response.json}", - query_timestamps=[q.request.timestamp], - ) + self._no_auth_session = no_auth_session + self._invalid_token_session = invalid_token_session - def _verify_subscription_endpoints_authentication(self): + def verify_sub_endpoints_authentication(self): self._verify_sub_creation() self._verify_sub_get() self._verify_sub_mutation() self._verify_sub_deletion() - self._verify_search() + self._verify_sub_search() def _verify_sub_creation(self): # Subscription creation request: @@ -179,66 +79,49 @@ def _verify_sub_creation(self): ) # No auth: - no_auth_q = fetch.query_and_describe( - client=self._no_auth_session, **query_kwargs - ) - self.record_query(no_auth_q) - - with self.check("Create subscription with missing credentials") as check: + no_auth_q = self._gen_val.query_no_auth(**query_kwargs) + with self._scenario.check( + "Create subscription with missing credentials" + ) as check: if no_auth_q.status_code != 401: check.record_failed( summary=f"Expected 401, got {no_auth_q.status_code}", query_timestamps=[no_auth_q.request.timestamp], ) - self._sanity_check_not_created(check, no_auth_q) + self._sanity_check_sub_not_created(check, no_auth_q) - self._verify_error_response(no_auth_q) + self._gen_val.verify_4xx_response(no_auth_q) # Bad token signature: - invalid_token_q = fetch.query_and_describe( - client=self._invalid_token_session, - scope=Scope.StrategicCoordination, - **query_kwargs, - ) - self.record_query(invalid_token_q) - - with self.check("Create subscription with invalid credentials") as check: + invalid_token_q = self._gen_val.query_invalid_token(**query_kwargs) + with self._scenario.check( + "Create subscription with invalid credentials" + ) as check: if invalid_token_q.status_code != 401: check.record_failed( summary=f"Expected 401, got {invalid_token_q.status_code}", query_timestamps=[invalid_token_q.request.timestamp], ) - self._sanity_check_not_created(check, invalid_token_q) + self._sanity_check_sub_not_created(check, invalid_token_q) - self._verify_error_response(invalid_token_q) + self._gen_val.verify_4xx_response(invalid_token_q) # Valid credentials but missing scope: - no_scope_q = fetch.query_and_describe( - client=self._dss.client, - **query_kwargs, - ) - self.record_query(no_scope_q) - - with self.check("Create subscription with missing scope") as check: + no_scope_q = self._gen_val.query_missing_scope(**query_kwargs) + with self._scenario.check("Create subscription with missing scope") as check: if no_scope_q.status_code != 401: check.record_failed( summary=f"Expected 401, got {no_scope_q.status_code}", query_timestamps=[no_scope_q.request.timestamp], ) - self._sanity_check_not_created(check, no_scope_q) + self._sanity_check_sub_not_created(check, no_scope_q) # Valid credentials but wrong scope: - wrong_scope_q = fetch.query_and_describe( - client=self._dss.client, - scope=Scope.AvailabilityArbitration, # This scope should not be allowed to create subscriptions - **query_kwargs, - ) - self.record_query(wrong_scope_q) - - with self.check("Create subscription with incorrect scope") as check: + wrong_scope_q = self._gen_val.query_wrong_scope(**query_kwargs) + with self._scenario.check("Create subscription with incorrect scope") as check: if wrong_scope_q.status_code != 403: check.record_failed( summary=f"Expected 403, got {wrong_scope_q.status_code}", @@ -246,17 +129,15 @@ def _verify_sub_creation(self): query_timestamps=[wrong_scope_q.request.timestamp], ) - self._sanity_check_not_created(check, wrong_scope_q) + self._sanity_check_sub_not_created(check, wrong_scope_q) # Correct token: # - confirms that the request would otherwise work # - makes a subscription available for read/mutation tests - create_ok_q = fetch.query_and_describe( - client=self._dss.client, scope=Scope.StrategicCoordination, **query_kwargs - ) - self.record_query(create_ok_q) - - with self.check("Create subscription with valid credentials") as check: + create_ok_q = self._gen_val.query_valid_auth(**query_kwargs) + with self._scenario.check( + "Create subscription with valid credentials" + ) as check: if create_ok_q.status_code != 200: check.record_failed( summary=f"Expected 200, got {create_ok_q.status_code}", @@ -276,42 +157,50 @@ def _verify_sub_get(self): participant_id=self._dss.participant_id, ) - query_no_auth = fetch.query_and_describe( - client=self._no_auth_session, **query_kwargs - ) - self.record_query(query_no_auth) - - with self.check("Get subscription with missing credentials") as check: + query_no_auth = self._gen_val.query_no_auth(**query_kwargs) + with self._scenario.check("Get subscription with missing credentials") as check: if query_no_auth.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_no_auth.status_code}", query_timestamps=[query_no_auth.request.timestamp], ) - self._verify_error_response(query_no_auth) - - query_invalid_token = fetch.query_and_describe( - client=self._invalid_token_session, - scope=Scope.StrategicCoordination, - **query_kwargs, - ) - self.record_query(query_invalid_token) + self._gen_val.verify_4xx_response(query_no_auth) - with self.check("Get subscription with invalid credentials") as check: + query_invalid_token = self._gen_val.query_invalid_token(**query_kwargs) + with self._scenario.check("Get subscription with invalid credentials") as check: if query_invalid_token.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_invalid_token.status_code}", query_timestamps=[query_invalid_token.request.timestamp], ) - self._verify_error_response(query_invalid_token) + self._gen_val.verify_4xx_response(query_invalid_token) - query_ok = fetch.query_and_describe( - client=self._dss.client, scope=Scope.StrategicCoordination, **query_kwargs - ) - self.record_query(query_ok) + query_missing_scope = self._gen_val.query_missing_scope(**query_kwargs) + with self._scenario.check("Get subscription with missing scope") as check: + if query_missing_scope.status_code != 401: + check.record_failed( + summary=f"Expected 401, got {query_missing_scope.status_code}", + details="Without the proper scope, the DSS should return a 401 without any data.", + query_timestamps=[query_missing_scope.request.timestamp], + ) + + self._gen_val.verify_4xx_response(query_missing_scope) - with self.check("Get subscription with valid credentials") as check: + query_wrong_scope = self._gen_val.query_wrong_scope(**query_kwargs) + with self._scenario.check("Get subscription with incorrect scope") as check: + if query_wrong_scope.status_code != 403: + check.record_failed( + summary=f"Expected 403, got {query_wrong_scope.status_code}", + details="With an incorrect scope, the DSS should return a 403 without any data.", + query_timestamps=[query_wrong_scope.request.timestamp], + ) + + self._gen_val.verify_4xx_response(query_wrong_scope) + + query_ok = self._gen_val.query_valid_auth(**query_kwargs) + with self._scenario.check("Get subscription with valid credentials") as check: if query_ok.status_code != 200: check.record_failed( summary=f"Expected 200, got {query_ok.status_code}", @@ -337,47 +226,36 @@ def _verify_sub_mutation(self): participant_id=self._dss.participant_id, ) - no_auth_q = fetch.query_and_describe( - client=self._no_auth_session, **query_kwargs - ) - self.record_query(no_auth_q) - - with self.check("Mutate subscription with missing credentials") as check: + no_auth_q = self._gen_val.query_no_auth(**query_kwargs) + with self._scenario.check( + "Mutate subscription with missing credentials" + ) as check: if no_auth_q.status_code != 401: check.record_failed( summary=f"Expected 401, got {no_auth_q.status_code}", query_timestamps=[no_auth_q.request.timestamp], ) # Sanity check: confirm the subscription was not mutated by the faulty call: - self._sanity_check_not_mutated(check, no_auth_q) + self._sanity_check_sub_not_mutated(check, no_auth_q) - self._verify_error_response(no_auth_q) + self._gen_val.verify_4xx_response(no_auth_q) - invalid_token_q = fetch.query_and_describe( - client=self._invalid_token_session, - scope=Scope.StrategicCoordination, - **query_kwargs, - ) - self.record_query(invalid_token_q) - - with self.check("Mutate subscription with invalid credentials") as check: + invalid_token_q = self._gen_val.query_invalid_token(**query_kwargs) + with self._scenario.check( + "Mutate subscription with invalid credentials" + ) as check: if invalid_token_q.status_code != 401: check.record_failed( summary=f"Expected 401, got {invalid_token_q.status_code}", query_timestamps=[invalid_token_q.request.timestamp], ) # Sanity check: confirm the subscription was not mutated by the faulty call: - self._sanity_check_not_mutated(check, invalid_token_q) + self._sanity_check_sub_not_mutated(check, invalid_token_q) - self._verify_error_response(invalid_token_q) + self._gen_val.verify_4xx_response(invalid_token_q) - query_missing_scope = fetch.query_and_describe( - client=self._dss.client, - **query_kwargs, - ) - self.record_query(query_missing_scope) - - with self.check("Mutate subscription with missing scope") as check: + query_missing_scope = self._gen_val.query_missing_scope(**query_kwargs) + with self._scenario.check("Mutate subscription with missing scope") as check: if query_missing_scope.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_missing_scope.status_code}", @@ -385,18 +263,12 @@ def _verify_sub_mutation(self): query_timestamps=[query_missing_scope.request.timestamp], ) # Sanity check: confirm the subscription was not mutated by the faulty call: - self._sanity_check_not_mutated(check, query_missing_scope) + self._sanity_check_sub_not_mutated(check, query_missing_scope) - self._verify_error_response(query_missing_scope) - - query_wrong_scope = fetch.query_and_describe( - client=self._dss.client, - scope=Scope.AvailabilityArbitration, - **query_kwargs, - ) - self.record_query(query_wrong_scope) + self._gen_val.verify_4xx_response(query_missing_scope) - with self.check("Mutate subscription with incorrect scope") as check: + query_wrong_scope = self._gen_val.query_wrong_scope(**query_kwargs) + with self._scenario.check("Mutate subscription with incorrect scope") as check: if query_wrong_scope.status_code != 403: check.record_failed( summary=f"Expected 403, got {query_wrong_scope.status_code}", @@ -404,14 +276,12 @@ def _verify_sub_mutation(self): query_timestamps=[query_wrong_scope.request.timestamp], ) # Sanity check: confirm the subscription was not mutated by the faulty call: - self._sanity_check_not_mutated(check, query_wrong_scope) + self._sanity_check_sub_not_mutated(check, query_wrong_scope) - mutate_ok_q = fetch.query_and_describe( - client=self._dss.client, scope=Scope.StrategicCoordination, **query_kwargs - ) - self.record_query(mutate_ok_q) - - with self.check("Mutate subscription with valid credentials") as check: + mutate_ok_q = self._gen_val.query_valid_auth(**query_kwargs) + with self._scenario.check( + "Mutate subscription with valid credentials" + ) as check: if mutate_ok_q.status_code != 200: check.record_failed( summary=f"Expected 200, got {mutate_ok_q.status_code}", @@ -433,47 +303,36 @@ def _verify_sub_deletion(self): participant_id=self._dss.participant_id, ) - query_no_auth = fetch.query_and_describe( - client=self._no_auth_session, **query_kwargs - ) - self.record_query(query_no_auth) - - with self.check("Delete subscription with missing credentials") as check: + query_no_auth = self._gen_val.query_no_auth(**query_kwargs) + with self._scenario.check( + "Delete subscription with missing credentials" + ) as check: if query_no_auth.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_no_auth.status_code}", query_timestamps=[query_no_auth.request.timestamp], ) # Sanity check - self._sanity_check_not_deleted(check, query_no_auth) + self._sanity_check_sub_not_deleted(check, query_no_auth) - self._verify_error_response(query_no_auth) + self._gen_val.verify_4xx_response(query_no_auth) - query_invalid_token = fetch.query_and_describe( - client=self._invalid_token_session, - scope=Scope.StrategicCoordination, - **query_kwargs, - ) - self.record_query(query_invalid_token) - - with self.check("Delete subscription with invalid credentials") as check: + query_invalid_token = self._gen_val.query_invalid_token(**query_kwargs) + with self._scenario.check( + "Delete subscription with invalid credentials" + ) as check: if query_invalid_token.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_invalid_token.status_code}", query_timestamps=[query_invalid_token.request.timestamp], ) # Sanity check - self._sanity_check_not_deleted(check, query_invalid_token) + self._sanity_check_sub_not_deleted(check, query_invalid_token) - self._verify_error_response(query_invalid_token) + self._gen_val.verify_4xx_response(query_invalid_token) - query_missing_scope = fetch.query_and_describe( - client=self._dss.client, - **query_kwargs, - ) - self.record_query(query_missing_scope) - - with self.check("Delete subscription with missing scope") as check: + query_missing_scope = self._gen_val.query_missing_scope(**query_kwargs) + with self._scenario.check("Delete subscription with missing scope") as check: if query_missing_scope.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_missing_scope.status_code}", @@ -481,18 +340,12 @@ def _verify_sub_deletion(self): query_timestamps=[query_missing_scope.request.timestamp], ) # Sanity check - self._sanity_check_not_deleted(check, query_missing_scope) + self._sanity_check_sub_not_deleted(check, query_missing_scope) - self._verify_error_response(query_missing_scope) - - query_wrong_scope = fetch.query_and_describe( - client=self._dss.client, - scope=Scope.AvailabilityArbitration, - **query_kwargs, - ) - self.record_query(query_wrong_scope) + self._gen_val.verify_4xx_response(query_missing_scope) - with self.check("Delete subscription with incorrect scope") as check: + query_wrong_scope = self._gen_val.query_wrong_scope(**query_kwargs) + with self._scenario.check("Delete subscription with incorrect scope") as check: if query_wrong_scope.status_code != 403: check.record_failed( summary=f"Expected 403, got {query_wrong_scope.status_code}", @@ -500,16 +353,14 @@ def _verify_sub_deletion(self): query_timestamps=[query_wrong_scope.request.timestamp], ) # Sanity check - self._sanity_check_not_deleted(check, query_wrong_scope) + self._sanity_check_sub_not_deleted(check, query_wrong_scope) - self._verify_error_response(query_wrong_scope) + self._gen_val.verify_4xx_response(query_wrong_scope) - query_ok = fetch.query_and_describe( - client=self._dss.client, scope=Scope.StrategicCoordination, **query_kwargs - ) - self.record_query(query_ok) - - with self.check("Delete subscription with valid credentials") as check: + query_ok = self._gen_val.query_valid_auth(**query_kwargs) + with self._scenario.check( + "Delete subscription with valid credentials" + ) as check: if query_ok.status_code != 200: check.record_failed( summary=f"Expected 200, got {query_ok.status_code}", @@ -518,7 +369,9 @@ def _verify_sub_deletion(self): # Confirm the subscription was deleted not_found = self._dss.get_subscription(self._sub_params.sub_id) - with self.check("Delete subscription with valid credentials") as check: + with self._scenario.check( + "Delete subscription with valid credentials" + ) as check: if not_found.status_code != 404: check.record_failed( summary=f"Expected 404, got {not_found.status_code}", @@ -529,7 +382,7 @@ def _verify_sub_deletion(self): ], ) - def _verify_search(self): + def _verify_sub_search(self): op = OPERATIONS[OperationID.QuerySubscriptions] query_kwargs = dict( @@ -542,12 +395,10 @@ def _verify_search(self): participant_id=self._dss.participant_id, ) - query_no_auth = fetch.query_and_describe( - client=self._no_auth_session, **query_kwargs - ) - self.record_query(query_no_auth) - - with self.check("Search subscriptions with missing credentials") as check: + query_no_auth = self._gen_val.query_no_auth(**query_kwargs) + with self._scenario.check( + "Search subscriptions with missing credentials" + ) as check: if query_no_auth.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_no_auth.status_code}", @@ -555,16 +406,12 @@ def _verify_search(self): query_timestamps=[query_no_auth.request.timestamp], ) - self._verify_error_response(query_no_auth) - - query_invalid_token = fetch.query_and_describe( - client=self._invalid_token_session, - scope=Scope.StrategicCoordination, - **query_kwargs, - ) - self.record_query(query_invalid_token) + self._gen_val.verify_4xx_response(query_no_auth) - with self.check("Search subscriptions with invalid credentials") as check: + query_invalid_token = self._gen_val.query_invalid_token(**query_kwargs) + with self._scenario.check( + "Search subscriptions with invalid credentials" + ) as check: if query_invalid_token.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_invalid_token.status_code}", @@ -572,15 +419,10 @@ def _verify_search(self): query_timestamps=[query_invalid_token.request.timestamp], ) - self._verify_error_response(query_invalid_token) - - query_missing_scope = fetch.query_and_describe( - client=self._dss.client, - **query_kwargs, - ) - self.record_query(query_missing_scope) + self._gen_val.verify_4xx_response(query_invalid_token) - with self.check("Search subscriptions with missing scope") as check: + query_missing_scope = self._gen_val.query_missing_scope(**query_kwargs) + with self._scenario.check("Search subscriptions with missing scope") as check: if query_missing_scope.status_code != 401: check.record_failed( summary=f"Expected 401, got {query_missing_scope.status_code}", @@ -588,16 +430,10 @@ def _verify_search(self): query_timestamps=[query_missing_scope.request.timestamp], ) - self._verify_error_response(query_missing_scope) + self._gen_val.verify_4xx_response(query_missing_scope) - query_wrong_scope = fetch.query_and_describe( - client=self._dss.client, - scope=Scope.AvailabilityArbitration, - **query_kwargs, - ) - self.record_query(query_wrong_scope) - - with self.check("Search subscriptions with incorrect scope") as check: + query_wrong_scope = self._gen_val.query_wrong_scope(**query_kwargs) + with self._scenario.check("Search subscriptions with incorrect scope") as check: if query_wrong_scope.status_code != 403: check.record_failed( summary=f"Expected 403, got {query_wrong_scope.status_code}", @@ -605,23 +441,23 @@ def _verify_search(self): query_timestamps=[query_wrong_scope.request.timestamp], ) - self._verify_error_response(query_wrong_scope) - - query_ok = fetch.query_and_describe( - client=self._dss.client, scope=Scope.StrategicCoordination, **query_kwargs - ) - self.record_query(query_ok) + self._gen_val.verify_4xx_response(query_wrong_scope) - with self.check("Search subscriptions with valid credentials") as check: + query_ok = self._gen_val.query_valid_auth(**query_kwargs) + with self._scenario.check( + "Search subscriptions with valid credentials" + ) as check: if query_ok.status_code != 200: check.record_failed( summary=f"Expected 200, got {query_ok.status_code}", query_timestamps=[query_ok.request.timestamp], ) - def _sanity_check_not_created(self, check: PendingCheck, creation_q: fetch.Query): + def _sanity_check_sub_not_created( + self, check: PendingCheck, creation_q: fetch.Query + ): sanity_check = self._dss.get_subscription(self._sub_params.sub_id) - self.record_query(sanity_check) + self._scenario.record_query(sanity_check) if sanity_check.status_code != 404: check.record_failed( summary=f"Subscription was created by an unauthorized request.", @@ -631,11 +467,13 @@ def _sanity_check_not_created(self, check: PendingCheck, creation_q: fetch.Query sanity_check.request.timestamp, ], ) - self._verify_error_response(sanity_check) + self._gen_val.verify_4xx_response(sanity_check) - def _sanity_check_not_mutated(self, check: PendingCheck, mutation_q: fetch.Query): + def _sanity_check_sub_not_mutated( + self, check: PendingCheck, mutation_q: fetch.Query + ): sanity_check = self._dss.get_subscription(self._sub_params.sub_id) - self.record_query(sanity_check) + self._scenario.record_query(sanity_check) if ( abs( sanity_check.subscription.time_end.value.datetime @@ -652,9 +490,11 @@ def _sanity_check_not_mutated(self, check: PendingCheck, mutation_q: fetch.Query ], ) - def _sanity_check_not_deleted(self, check: PendingCheck, deletion_q: fetch.Query): + def _sanity_check_sub_not_deleted( + self, check: PendingCheck, deletion_q: fetch.Query + ): sanity_check = self._dss.get_subscription(self._sub_params.sub_id) - self.record_query(sanity_check) + self._scenario.record_query(sanity_check) if sanity_check.status_code == 404: check.record_failed( summary=f"Unauthorized request could delete the subscription.", @@ -673,8 +513,3 @@ def _sanity_check_not_deleted(self, check: PendingCheck, deletion_q: fetch.Query sanity_check.request.timestamp, ], ) - - def cleanup(self): - self.begin_cleanup() - self._ensure_test_sub_ids_do_not_exist() - self.end_cleanup() diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md index 1fd165d82c..afd2306191 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md @@ -4,7 +4,7 @@ ## [Actions](../../README.md#actions) -1. Scenario: [ASTM SCD DSS: Interfaces authentication](../../../scenarios/astm/utm/dss/authentication_validation.md) ([`scenarios.astm.utm.dss.AuthenticationValidation`](../../../scenarios/astm/utm/dss/authentication_validation.py)) +1. Scenario: [ASTM SCD DSS: Interfaces authentication](../../../scenarios/astm/utm/dss/authentication/authentication_validation.md) ([`scenarios.astm.utm.dss.authentication.AuthenticationValidation`](../../../scenarios/astm/utm/dss/authentication/authentication_validation.py)) 2. Scenario: [ASTM SCD DSS: Subscription Simple](../../../scenarios/astm/utm/dss/subscription_simple.md) ([`scenarios.astm.utm.dss.SubscriptionSimple`](../../../scenarios/astm/utm/dss/subscription_simple.py)) 3. Scenario: [ASTM SCD DSS: Subscription Validation](../../../scenarios/astm/utm/dss/subscription_validation.md) ([`scenarios.astm.utm.dss.SubscriptionValidation`](../../../scenarios/astm/utm/dss/subscription_validation.py)) 4. Scenario: [ASTM F3548-21 UTM DSS Operational Intent Reference Access Control](../../../scenarios/astm/utm/op_intent_ref_access_control.md) ([`scenarios.astm.utm.OpIntentReferenceAccessControl`](../../../scenarios/astm/utm/op_intent_ref_access_control.py)) @@ -23,17 +23,17 @@