From f126f26b8a59c2fd13c197ebf9e17ee8753fa408 Mon Sep 17 00:00:00 2001 From: Julien Perrochet Date: Tue, 20 Feb 2024 14:40:12 +0100 Subject: [PATCH] DSS02120,A2-7-2,7 --- monitoring/monitorlib/mutate/scd.py | 53 +- monitoring/prober/infrastructure.py | 2 +- .../dev/f3548_self_contained.yaml | 3 + .../dev/library/environment_containers.yaml | 3 + .../dev/library/environment_localhost.yaml | 2 + .../resources/astm/f3548/v21/planning_area.py | 57 +- .../astm/f3548/v21/subscription_params.py | 28 + .../astm/utm/dss/authentication/__init__.py | 1 + .../authentication_validation.md | 175 ++++++ .../authentication_validation.py | 176 ++++++ .../astm/utm/dss/authentication/generic.py | 101 +++ .../dss/authentication/sub_api_validator.py | 576 ++++++++++++++++++ .../astm/utm/dss/subscription_validation.py | 1 + .../suites/astm/utm/dss_probing.md | 24 +- .../suites/astm/utm/dss_probing.yaml | 6 + .../uss_qualifier/suites/astm/utm/f3548_21.md | 13 +- .../suites/faa/uft/message_signing.md | 13 +- .../suites/interuss/dss/all_tests.md | 13 +- .../suites/uspace/flight_auth.md | 13 +- .../suites/uspace/required_services.md | 13 +- 20 files changed, 1226 insertions(+), 47 deletions(-) create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/__init__.py create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py create mode 100644 monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py diff --git a/monitoring/monitorlib/mutate/scd.py b/monitoring/monitorlib/mutate/scd.py index 40c1e67eea..3543fe2c58 100644 --- a/monitoring/monitorlib/mutate/scd.py +++ b/monitoring/monitorlib/mutate/scd.py @@ -4,7 +4,12 @@ import s2sphere import yaml from implicitdict import ImplicitDict -from uas_standards.astm.f3548.v21.api import OPERATIONS, OperationID, Subscription +from uas_standards.astm.f3548.v21.api import ( + OPERATIONS, + OperationID, + Subscription, + PutSubscriptionParameters, +) from yaml.representer import Representer from monitoring.monitorlib import fetch @@ -74,18 +79,16 @@ def upsert_subscription( path = op.path.format(subscriptionid=subscription_id, version=version) query_type = QueryType.F3548v21DSSUpdateSubscription - body = { - "extents": Volume4D.from_values( - start_time, - end_time, - min_alt_m, - max_alt_m, - polygon=Polygon.from_latlng_rect(latlngrect=area), - ).to_f3548v21(), - "uss_base_url": base_url, - "notify_for_operational_intents": notify_for_op_intents, - "notify_for_constraints": notify_for_constraints, - } + body = build_upsert_subscription_params( + area_vertices=area, + start_time=start_time, + end_time=end_time, + base_url=base_url, + notify_for_op_intents=notify_for_op_intents, + notify_for_constraints=notify_for_constraints, + min_alt_m=min_alt_m, + max_alt_m=max_alt_m, + ) result = MutatedSubscription( fetch.query_and_describe( @@ -102,6 +105,30 @@ def upsert_subscription( return result +def build_upsert_subscription_params( + area_vertices: s2sphere.LatLngRect, + start_time: datetime.datetime, + end_time: datetime.datetime, + base_url: str, + notify_for_op_intents: bool, + notify_for_constraints: bool, + min_alt_m: float, + max_alt_m: float, +) -> PutSubscriptionParameters: + return PutSubscriptionParameters( + extents=Volume4D.from_values( + start_time, + end_time, + min_alt_m, + max_alt_m, + polygon=Polygon.from_latlng_rect(latlngrect=area_vertices), + ).to_f3548v21(), + uss_base_url=base_url, + notify_for_operational_intents=notify_for_op_intents, + notify_for_constraints=notify_for_constraints, + ) + + def delete_subscription( utm_client: infrastructure.UTMClientSession, subscription_id: str, diff --git a/monitoring/prober/infrastructure.py b/monitoring/prober/infrastructure.py index d543476a4f..95e3aac299 100644 --- a/monitoring/prober/infrastructure.py +++ b/monitoring/prober/infrastructure.py @@ -100,7 +100,7 @@ def wrapper_default_scope(*args, **kwargs): resource_type_code_descriptions: Dict[ResourceType, str] = {} -# Next code: 380 +# Next code: 381 def register_resource_type(code: int, description: str) -> ResourceType: """Register that the specified code refers to the described resource. diff --git a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml index c5d165c3fe..e607364cec 100644 --- a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml +++ b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml @@ -48,6 +48,9 @@ v1: # ASTM F3548-21 USS emulation roles - utm.strategic_coordination - utm.availability_arbitration + # For authentication test purposes. + # Remove if the authentication provider pointed to by AUTH_SPEC does not support it. + - "" # Means by which uss_qualifier can discover which subscription ('sub' claim of its tokes) it is described by utm_client_identity: diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml index 1f9cd7378a..594b49d251 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_containers.yaml @@ -29,6 +29,9 @@ utm_auth: - utm.availability_arbitration # InterUSS versioning automated testing - interuss.versioning.read_system_versions + # For authentication test purposes. + # Remove if the authentication provider pointed to by AUTH_SPEC does not support it. + - "" second_utm_auth: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json diff --git a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml index 0a8ba9f8af..e3d740c8a9 100644 --- a/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml +++ b/monitoring/uss_qualifier/configurations/dev/library/environment_localhost.yaml @@ -29,6 +29,8 @@ utm_auth: - utm.availability_arbitration # InterUSS versioning automated testing - interuss.versioning.read_system_versions + # For authentication test purposes + - "" second_utm_auth: $content_schema: monitoring/uss_qualifier/resources/definitions/ResourceDeclaration.json 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/resources/astm/f3548/v21/subscription_params.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/subscription_params.py index b230563688..f02485d706 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/subscription_params.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/subscription_params.py @@ -1,9 +1,12 @@ import datetime from typing import List, Optional, Self +import s2sphere from implicitdict import ImplicitDict +from uas_standards.astm.f3548.v21.api import PutSubscriptionParameters from monitoring.monitorlib.geo import LatLngPoint +from monitoring.monitorlib.mutate import scd as mutate class SubscriptionParams(ImplicitDict): @@ -44,3 +47,28 @@ class SubscriptionParams(ImplicitDict): def copy(self) -> Self: return SubscriptionParams(super().copy()) + + def to_upsert_subscription_params( + self, area: s2sphere.LatLngRect + ) -> PutSubscriptionParameters: + """ + Prepares the subscription parameters to be used in the body of an HTTP request + to create or update a subscription on the DSS in the SCD context. + + Args: + area: area to include in the subscription parameters + + Returns: + A dict to be passed as the request body when calling the subscription creation or update API. + + """ + return mutate.build_upsert_subscription_params( + area_vertices=area, + start_time=self.start_time, + end_time=self.end_time, + base_url=self.base_url, + notify_for_op_intents=self.notify_for_op_intents, + notify_for_constraints=self.notify_for_constraints, + min_alt_m=self.min_alt_m, + max_alt_m=self.max_alt_m, + ) 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/authentication_validation.md b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md new file mode 100644 index 0000000000..efca4dc598 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md @@ -0,0 +1,175 @@ +# ASTM SCD DSS: Interfaces authentication test scenario + +## Overview + +Ensures that a DSS properly authenticates requests to all its endpoints. + +Note that this does not cover authorization. + +## Resources + +### dss + +[`DSSInstanceResource`](../../../../../resources/astm/f3548/v21/dss.py) to be tested in this scenario. + +Note that to benefit from the maximum coverage, the DSS' AuthAdapterResource must be able to obtain credentials +for multiple scopes (so that a wrong scope may be used in place of the correct one) as well as an empty scope (that is, provide credentials where the scope is an empty string). + +This scenario will check for the scope's availability and transparently ignore checks that can't be conducted. + +The scopes the scenario is expected to be allowed to use are: + +- `utm.strategic_coordination` +- `utm.availability_arbitration` +- `""` (empty string) + +### id_generator + +[`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. + +## Setup test case + +### [Ensure clean workspace test step](../clean_workspace.md) + +This step ensures that no entity with the known test IDs exists in the DSS. + +## Endpoint authorization test case + +This test case ensures that the DSS properly authenticates requests to all its endpoints. + +### Subscription endpoints authentication test step + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +#### 🛑 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)**. + +## [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..5f42b398a9 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py @@ -0,0 +1,176 @@ +from datetime import datetime, timedelta + +from uas_standards.astm.f3548.v21.constants import ( + Scope, +) + +from monitoring.monitorlib.auth import InvalidTokenSignatureAuth +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, +) +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"} + # We use the AvailabilityArbitration scope as the 'wrong' scope for some tests + # this checks if we are allowed to use it + self._wrong_scope = None + if dss.can_use_scope(Scope.AvailabilityArbitration): + scopes[ + Scope.AvailabilityArbitration + ] = "Attempt to query subscriptions with wrong scope" + self._wrong_scope = Scope.AvailabilityArbitration + + self._test_missing_scope = False + if dss.can_use_scope(""): + scopes[""] = "Attempt to query subscriptions with missing scope" + self._test_missing_scope = True + + # 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, Scope.StrategicCoordination + ) + + self._sub_validator = SubscriptionAuthValidator( + scenario=self, + generic_validator=generic_validator, + dss=self._dss, + test_id=self._test_id, + planning_area=self._planning_area, + planning_area_volume4d=self._planning_area_volume4d, + no_auth_session=self._no_auth_session, + invalid_token_session=self._invalid_token_session, + test_wrong_scope=self._wrong_scope, + test_missing_scope=self._test_missing_scope, + ) + + def run(self, context: ExecutionContext): + self.begin_test_scenario(context) + self._setup_case() + self.begin_test_case("Endpoint authorization") + + if self._wrong_scope: + self.record_note( + "wrong_scope", + f"Incorrect scope testing enabled with scope {self._wrong_scope}.", + ) + else: + self.record_note("wrong_scope", "Incorrect scope testing disabled.") + + if self._test_missing_scope: + self.record_note("missing_scope", "Missing scope testing enabled.") + else: + self.record_note("missing_scope", "Missing scope testing disabled.") + + 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..9dbcb4eaed --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py @@ -0,0 +1,101 @@ +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, + valid_scope: Scope, + ): + 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) + self._valid_scope = valid_scope + + 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, scope: str, **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. + Note that the auth adapter needs to be able to request a token with this scope. + """ + q = fetch.query_and_describe( + client=self._authenticated_session, + scope=scope, + **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=self._valid_scope, + **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/sub_api_validator.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py new file mode 100644 index 0000000000..c31cc89bb8 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/sub_api_validator.py @@ -0,0 +1,576 @@ +from datetime import datetime, timedelta +from typing import Optional + +from uas_standards.astm.f3548.v21.api import ( + OPERATIONS, + OperationID, + QuerySubscriptionParameters, +) +from uas_standards.astm.f3548.v21.constants import Scope + +from monitoring.monitorlib import fetch +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.uss_qualifier.resources.astm.f3548.v21.dss import DSSInstance +from monitoring.uss_qualifier.resources.astm.f3548.v21.planning_area import ( + PlanningAreaSpecification, +) +from monitoring.uss_qualifier.scenarios.astm.utm.dss.authentication.generic import ( + GenericAuthValidator, +) +from monitoring.uss_qualifier.scenarios.scenario import TestScenario, PendingCheck + +TIME_TOLERANCE_SEC = 1 + + +class SubscriptionAuthValidator: + def __init__( + self, + 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, + test_wrong_scope: Optional[str] = None, + test_missing_scope: bool = False, + ): + """ + + Args: + scenario: Scenario on which the checks will be done + generic_validator: Provides generic verification methods for DSS API calls + dss: the DSS instance being tested + test_id: identifier to use for the subscriptions that will be created + planning_area: the planning area to use for the subscriptions + planning_area_volume4d: a volume 4d encompassing the planning area + no_auth_session: an unauthenticated session + invalid_token_session: a session using a well-formed token that has an invalid signature + test_wrong_scope: a valid scope that is not allowed to perform operations on subscriptions, if available. + If None, checks using a wrong scope will be skipped. + test_missing_scope: if True, will attempt to perform operations without specifying a scope using the valid credentials. + """ + 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._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, + ) + + self._no_auth_session = no_auth_session + self._invalid_token_session = invalid_token_session + + self._test_wrong_scope = test_wrong_scope + self._test_missing_scope = test_missing_scope + + def verify_sub_endpoints_authentication(self): + self._verify_sub_creation() + self._verify_sub_get() + self._verify_sub_mutation() + self._verify_sub_deletion() + self._verify_sub_search() + + def _verify_sub_creation(self): + # Subscription creation request: + sub_params = dict(**self._sub_params) + op = OPERATIONS[OperationID.CreateSubscription] + del sub_params["sub_id"] + query_kwargs = dict( + verb=op.verb, + url=op.path.format(subscriptionid=self._sub_params.sub_id), + json=mutate.build_upsert_subscription_params(**sub_params), + query_type=QueryType.F3548v21DSSCreateSubscription, + participant_id=self._dss.participant_id, + ) + + # No auth: + 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_sub_not_created(check, no_auth_q) + + self._gen_val.verify_4xx_response(no_auth_q) + + # Bad token signature: + 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_sub_not_created(check, invalid_token_q) + + self._gen_val.verify_4xx_response(invalid_token_q) + + # Valid credentials but missing scope: + if self._test_missing_scope: + 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_sub_not_created(check, no_scope_q) + self._gen_val.verify_4xx_response(no_scope_q) + + # Valid credentials but wrong scope: + if self._test_wrong_scope: + wrong_scope_q = self._gen_val.query_wrong_scope( + scope=self._test_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}", + details="With an incorrect scope, the DSS should return a 403 without any data.", + query_timestamps=[wrong_scope_q.request.timestamp], + ) + + self._sanity_check_sub_not_created(check, wrong_scope_q) + self._gen_val.verify_4xx_response(wrong_scope_q) + + # Correct token: + # - confirms that the request would otherwise work + # - makes a subscription available for read/mutation tests + 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}", + query_timestamps=[create_ok_q.request.timestamp], + ) + + # Store the subscription + self._current_sub = MutatedSubscription(create_ok_q).subscription + + def _verify_sub_get(self): + op = OPERATIONS[OperationID.GetSubscription] + + query_kwargs = dict( + verb=op.verb, + url=op.path.format(subscriptionid=self._sub_params.sub_id), + query_type=QueryType.F3548v21DSSGetSubscription, + participant_id=self._dss.participant_id, + ) + + 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._gen_val.verify_4xx_response(query_no_auth) + + 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._gen_val.verify_4xx_response(query_invalid_token) + + if self._test_missing_scope: + 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) + + if self._test_wrong_scope: + query_wrong_scope = self._gen_val.query_wrong_scope( + scope=self._test_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}", + query_timestamps=[query_ok.request.timestamp], + ) + + def _verify_sub_mutation(self): + # Subscription creation request: + new_params = self._sub_params.copy() + new_params.end_time = new_params.end_time - timedelta(seconds=10) + pld_params = dict(**new_params) + op = OPERATIONS[OperationID.UpdateSubscription] + del pld_params["sub_id"] + + query_kwargs = dict( + verb=op.verb, + url=op.path.format( + subscriptionid=self._sub_params.sub_id, + version=self._current_sub.version, + ), + json=mutate.build_upsert_subscription_params(**pld_params), + query_type=QueryType.F3548v21DSSCreateSubscription, + participant_id=self._dss.participant_id, + ) + + 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_sub_not_mutated(check, no_auth_q) + + self._gen_val.verify_4xx_response(no_auth_q) + + 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_sub_not_mutated(check, invalid_token_q) + + self._gen_val.verify_4xx_response(invalid_token_q) + + if self._test_missing_scope: + 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}", + details="Without the proper scope, the DSS should return a 401 without any data.", + query_timestamps=[query_missing_scope.request.timestamp], + ) + # Sanity check: confirm the subscription was not mutated by the faulty call: + self._sanity_check_sub_not_mutated(check, query_missing_scope) + + self._gen_val.verify_4xx_response(query_missing_scope) + + if self._test_wrong_scope: + query_wrong_scope = self._gen_val.query_wrong_scope( + scope=self._test_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}", + details="With an incorrect scope, the DSS should return a 403 without any data.", + query_timestamps=[query_wrong_scope.request.timestamp], + ) + # Sanity check: confirm the subscription was not mutated by the faulty call: + self._sanity_check_sub_not_mutated(check, query_wrong_scope) + self._gen_val.verify_4xx_response(query_wrong_scope) + + 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}", + query_timestamps=[mutate_ok_q.request.timestamp], + ) + + self._current_sub = MutatedSubscription(mutate_ok_q).subscription + + def _verify_sub_deletion(self): + op = OPERATIONS[OperationID.DeleteSubscription] + + query_kwargs = dict( + verb=op.verb, + url=op.path.format( + subscriptionid=self._sub_params.sub_id, + version=self._current_sub.version, + ), + query_type=QueryType.F3548v21DSSDeleteSubscription, + participant_id=self._dss.participant_id, + ) + + 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_sub_not_deleted(check, query_no_auth) + + self._gen_val.verify_4xx_response(query_no_auth) + + 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_sub_not_deleted(check, query_invalid_token) + + self._gen_val.verify_4xx_response(query_invalid_token) + + if self._test_missing_scope: + 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}", + details="Without the proper scope, the DSS should return a 401 without any data.", + query_timestamps=[query_missing_scope.request.timestamp], + ) + # Sanity check + self._sanity_check_sub_not_deleted(check, query_missing_scope) + + self._gen_val.verify_4xx_response(query_missing_scope) + + if self._test_wrong_scope: + query_wrong_scope = self._gen_val.query_wrong_scope( + scope=self._test_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}", + details="With an incorrect scope, the DSS should return a 403 without any data.", + query_timestamps=[query_wrong_scope.request.timestamp], + ) + # Sanity check + self._sanity_check_sub_not_deleted(check, query_wrong_scope) + + self._gen_val.verify_4xx_response(query_wrong_scope) + + 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}", + query_timestamps=[query_ok.request.timestamp], + ) + + # Confirm the subscription was deleted + not_found = self._dss.get_subscription(self._sub_params.sub_id) + 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}", + details="The subscription should have been deleted, as the deletion attempt was appropriately authenticated.", + query_timestamps=[ + query_ok.request.timestamp, + not_found.request.timestamp, + ], + ) + + def _verify_sub_search(self): + op = OPERATIONS[OperationID.QuerySubscriptions] + + query_kwargs = dict( + verb=op.verb, + url=op.path, + query_type=QueryType.F3548v21DSSQuerySubscriptions, + json=QuerySubscriptionParameters( + area_of_interest=self._planning_area_volume4d + ), + participant_id=self._dss.participant_id, + ) + + 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}", + details="Without valid credentials, the DSS should return a 401 without any data.", + query_timestamps=[query_no_auth.request.timestamp], + ) + + self._gen_val.verify_4xx_response(query_no_auth) + + 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}", + details="Without valid credentials, the DSS should return a 401 without any data.", + query_timestamps=[query_invalid_token.request.timestamp], + ) + + self._gen_val.verify_4xx_response(query_invalid_token) + + if self._test_missing_scope: + 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}", + 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) + + if self._test_wrong_scope: + query_wrong_scope = self._gen_val.query_wrong_scope( + scope=self._test_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}", + 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( + "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_sub_not_created( + self, check: PendingCheck, creation_q: fetch.Query + ): + sanity_check = self._dss.get_subscription(self._sub_params.sub_id) + self._scenario.record_query(sanity_check) + if sanity_check.status_code != 404: + check.record_failed( + summary=f"Subscription was created by an unauthorized request.", + details="The subscription should not have been created, as the creation attempt was not authenticated.", + query_timestamps=[ + creation_q.request.timestamp, + sanity_check.request.timestamp, + ], + ) + self._gen_val.verify_4xx_response(sanity_check) + + 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._scenario.record_query(sanity_check) + if ( + abs( + sanity_check.subscription.time_end.value.datetime + - self._current_sub.time_end.value.datetime + ).total_seconds() + > TIME_TOLERANCE_SEC + ): + check.record_failed( + summary=f"Subscription was mutated by an unauthorized query.", + details="The subscription should not have been mutated, as the mutation attempt was not appropriately authenticated.", + query_timestamps=[ + mutation_q.request.timestamp, + sanity_check.request.timestamp, + ], + ) + + 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._scenario.record_query(sanity_check) + if sanity_check.status_code == 404: + check.record_failed( + summary=f"Unauthorized request could delete the subscription.", + details="The subscription should not have been deleted, as the deletion attempt was not authenticated.", + query_timestamps=[ + deletion_q.request.timestamp, + sanity_check.request.timestamp, + ], + ) + elif sanity_check.status_code != 200: + check.record_failed( + summary=f"Expected 200, got {sanity_check.status_code}", + details="The subscription should not have been deleted, as the deletion attempt was not appropriately authenticated.", + query_timestamps=[ + deletion_q.request.timestamp, + sanity_check.request.timestamp, + ], + ) diff --git a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py index 912de4fade..d1c5f6be02 100644 --- a/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/subscription_validation.py @@ -69,6 +69,7 @@ def __init__( """ 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] diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md index a17f95983f..6403fc9923 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md @@ -4,11 +4,12 @@ ## [Actions](../../README.md#actions) -1. 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)) -2. 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)) -3. 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)) -4. Scenario: [ASTM F3548-21 UTM DSS interoperability](../../../scenarios/astm/utm/dss_interoperability.md) ([`scenarios.astm.utm.DSSInteroperability`](../../../scenarios/astm/utm/dss_interoperability.py)) -5. Scenario: [ASTM SCD DSS: Subscription Synchronization](../../../scenarios/astm/utm/dss/subscription_synchronization.md) ([`scenarios.astm.utm.dss.SubscriptionSynchronization`](../../../scenarios/astm/utm/dss/subscription_synchronization.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)) +5. Scenario: [ASTM F3548-21 UTM DSS interoperability](../../../scenarios/astm/utm/dss_interoperability.md) ([`scenarios.astm.utm.DSSInteroperability`](../../../scenarios/astm/utm/dss_interoperability.py)) +6. Scenario: [ASTM SCD DSS: Subscription Synchronization](../../../scenarios/astm/utm/dss/subscription_synchronization.md) ([`scenarios.astm.utm.dss.SubscriptionSynchronization`](../../../scenarios/astm/utm/dss/subscription_synchronization.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -20,20 +21,20 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0005,2 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0005,5 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0015 @@ -75,6 +76,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0300 Implemented diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml index 4739938883..6bdcc0eebd 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml @@ -8,6 +8,12 @@ resources: planning_area: resources.astm.f3548.v21.PlanningAreaResource problematically_big_area: resources.VerticesResource actions: + - test_scenario: + scenario_type: scenarios.astm.utm.dss.authentication.AuthenticationValidation + resources: + dss: dss + id_generator: id_generator + planning_area: planning_area - test_scenario: scenario_type: scenarios.astm.utm.dss.SubscriptionSimple resources: diff --git a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md index 4912c412fc..8fc928f0c0 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -32,20 +32,20 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0005,5 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0015 @@ -92,6 +92,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0300 Implemented diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md index aeef4c677e..ae66fbd646 100644 --- a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md +++ b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md @@ -18,20 +18,20 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0005,5 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0015 @@ -78,6 +78,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0300 Implemented diff --git a/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md b/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md index e9180c7951..0f722afd93 100644 --- a/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md +++ b/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md @@ -408,20 +408,20 @@ ASTM NetRID DSS: Concurrent Requests
ASTM NetRID DSS: ISA Expiry
ASTM NetRID DSS: ISA Subscription Interactions
ASTM NetRID DSS: Simple ISA
ASTM NetRID DSS: Submitted ISA Validations
ASTM NetRID DSS: Subscription Simple
ASTM NetRID DSS: Subscription Validation
ASTM NetRID DSS: Token Validation - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0005,2 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0005,5 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0015 @@ -463,6 +463,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0300 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.md b/monitoring/uss_qualifier/suites/uspace/flight_auth.md index a82613f1b5..28836cd3b6 100644 --- a/monitoring/uss_qualifier/suites/uspace/flight_auth.md +++ b/monitoring/uss_qualifier/suites/uspace/flight_auth.md @@ -19,20 +19,20 @@ Checked in - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0005,5 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0015 @@ -79,6 +79,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0300 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index 0acae0f2c1..3438a02847 100644 --- a/monitoring/uss_qualifier/suites/uspace/required_services.md +++ b/monitoring/uss_qualifier/suites/uspace/required_services.md @@ -454,20 +454,20 @@ ASTM NetRID DSS: Concurrent Requests
ASTM NetRID DSS: ISA Expiry
ASTM NetRID DSS: ISA Subscription Interactions
ASTM NetRID DSS: Simple ISA
ASTM NetRID DSS: Submitted ISA Validations
ASTM NetRID DSS: Subscription Simple
ASTM NetRID DSS: Subscription Validation
ASTM NetRID DSS: Token Validation - astm
.f3548
.v21
+ astm
.f3548
.v21
DSS0005,1 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted DSS0005,2 Implemented - ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents + ASTM F3548 flight planners preparation
ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation
Data Validation of GET operational intents by USS
Nominal planning: conflict with higher priority
Nominal planning: not permitted conflict with equal priority
Off-Nominal planning: down USS
Off-Nominal planning: down USS with equal priority conflicts not permitted
Validation of operational intents DSS0005,5 Implemented - ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation + ASTM F3548-21 UTM DSS Operational Intent Reference Access Control
ASTM SCD DSS: Interfaces authentication
ASTM SCD DSS: Subscription Simple
ASTM SCD DSS: Subscription Synchronization
ASTM SCD DSS: Subscription Validation DSS0015 @@ -514,6 +514,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0300 Implemented