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/monitorlib/schema_validation.py b/monitoring/monitorlib/schema_validation.py index 8865e09914..72bffa250c 100644 --- a/monitoring/monitorlib/schema_validation.py +++ b/monitoring/monitorlib/schema_validation.py @@ -50,6 +50,7 @@ class F3411_22a(str, Enum): class F3548_21(str, Enum): OpenAPIPath = "interfaces/astm-utm/Protocol/utm.yaml" + ErrorResponse = "components.schemas.ErrorResponse" GetOperationalIntentDetailsResponse = ( "components.schemas.GetOperationalIntentDetailsResponse" ) 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 c55511801d..c5a5b232fd 100644 --- a/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml +++ b/monitoring/uss_qualifier/configurations/dev/f3548_self_contained.yaml @@ -49,6 +49,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 e4acbf28f7..86e07c1a06 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 85846e5841..4141845f77 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/dss.py b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py index b0bc9fbef1..6b6afdd8ec 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/dss.py @@ -510,6 +510,9 @@ def __init__( def can_use_scope(self, scope: str) -> bool: return scope in self._auth_adapter.scopes + def get_authorized_scopes(self) -> Set[str]: + return self._auth_adapter.scopes.copy() + def get_instance(self, scopes_required: Dict[str, str]) -> DSSInstance: """Get a client object ready to be used. 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 77fda09f73..3aab48a5f9 100644 --- a/monitoring/uss_qualifier/resources/astm/f3548/v21/planning_area.py +++ b/monitoring/uss_qualifier/resources/astm/f3548/v21/planning_area.py @@ -68,16 +68,18 @@ def get_new_operational_intent_ref_params( uss_base_url: UssBaseURL, time_start: datetime.datetime, time_end: datetime.datetime, - subscription_id: Optional[EntityID] = None, + subscription_id: Optional[EntityID], implicit_sub_base_url: Optional[UssBaseURL] = None, implicit_sub_for_constraints: Optional[bool] = None, ) -> PutOperationalIntentReferenceParameters: """ Builds 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. + + Note that this method allows building inconsistent parameters: """ return PutOperationalIntentReferenceParameters( extents=[ 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..387760901b --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.md @@ -0,0 +1,178 @@ +# 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. + +Required scopes for running this scenario: + +- `utm.strategic_coordination` + +Optional scopes that will allow the scenario to provide additional coverage: + +- `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..a1e6f31f84 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/authentication_validation.py @@ -0,0 +1,188 @@ +import random +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, + and properly validates the scopes of the requests depending on the action being performed. + + Note that this scenario does not verif that a DSS only allows an entity owner to modify the: + this is covered in other scenarios. + """ + + 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__() + # This is the proper scope for interactions with the DSS in this scenario + scopes = {Scope.StrategicCoordination: "create and delete subscriptions"} + + # For the 'wrong' scope we pick anything from the available scopes that isn't the SCD or empty scope: + available_scopes = dss.get_authorized_scopes() + available_scopes.discard(Scope.StrategicCoordination) + available_scopes.discard("") + + if len(available_scopes) > 0: + # Sort the scopes to obtain a deterministic order, pick the first one + available_scopes = sorted(available_scopes) + self._wrong_scope = available_scopes[0] + scopes[ + self._wrong_scope + ] = "Attempt to query subscriptions with wrong scope" + else: + self._wrong_scope = None + + self._test_missing_scope = False + if dss.can_use_scope(""): + scopes[""] = "Attempt to query subscriptions with missing scope" + self._test_missing_scope = True + + # Note: .get_instance should be called once we know every scope we will need, + # in order to guarantee that they are indeed available. + 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..da6f8df0f0 --- /dev/null +++ b/monitoring/uss_qualifier/scenarios/astm/utm/dss/authentication/generic.py @@ -0,0 +1,100 @@ +from uas_standards.astm.f3548.v21.constants import Scope + +from monitoring.monitorlib import fetch, schema_validation +from monitoring.monitorlib.auth import InvalidTokenSignatureAuth +from monitoring.monitorlib.infrastructure import UTMClientSession +from monitoring.monitorlib.schema_validation import F3548_21 +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=self._valid_scope, + **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, as per the OpenAPI spec.""" + + with self._scenario.check( + "Unauthorized requests return the proper error message body" + ) as check: + errors = schema_validation.validate( + F3548_21.OpenAPIPath, + F3548_21.ErrorResponse, + q.response.json, + ) + if errors: + check.record_failed( + summary="Unexpected error response body", + details=f"Response body for {q.request.method} query to {q.request.url} failed validation: {errors}, " + f"body content 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 bb254e1415..36af08e5d3 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.md @@ -4,12 +4,13 @@ ## [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/synchronization/subscription_synchronization.md) ([`scenarios.astm.utm.dss.synchronization.SubscriptionSynchronization`](../../../scenarios/astm/utm/dss/synchronization/subscription_synchronization.py)) -6. Scenario: [ASTM UTM DSS: Direct CRDB access](../../../scenarios/astm/utm/dss/crdb_access.md) ([`scenarios.astm.utm.dss.CRDBAccess`](../../../scenarios/astm/utm/dss/crdb_access.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/synchronization/subscription_synchronization.md) ([`scenarios.astm.utm.dss.synchronization.SubscriptionSynchronization`](../../../scenarios/astm/utm/dss/synchronization/subscription_synchronization.py)) +7. Scenario: [ASTM UTM DSS: Direct CRDB access](../../../scenarios/astm/utm/dss/crdb_access.md) ([`scenarios.astm.utm.dss.CRDBAccess`](../../../scenarios/astm/utm/dss/crdb_access.py)) ## [Checked requirements](../../README.md#checked-requirements) @@ -21,20 +22,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 @@ -96,6 +97,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0215 Implemented diff --git a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml index 46b4fbb4b1..87b3b1b6db 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml +++ b/monitoring/uss_qualifier/suites/astm/utm/dss_probing.yaml @@ -9,6 +9,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 fd553b4644..2a046e4d3e 100644 --- a/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md +++ b/monitoring/uss_qualifier/suites/astm/utm/f3548_21.md @@ -35,20 +35,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
Awareness of relevant operational intents
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
Awareness of relevant operational intents
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 @@ -115,6 +115,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0215 Implemented diff --git a/monitoring/uss_qualifier/suites/faa/uft/message_signing.md b/monitoring/uss_qualifier/suites/faa/uft/message_signing.md index 978831cb78..e0ae0dd8ed 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
Awareness of relevant operational intents
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
Awareness of relevant operational intents
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 @@ -98,6 +98,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0215 Implemented diff --git a/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md b/monitoring/uss_qualifier/suites/interuss/dss/all_tests.md index 809315c369..1bec59228b 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 @@ -483,6 +483,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0215 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/flight_auth.md b/monitoring/uss_qualifier/suites/uspace/flight_auth.md index 63d9ad2c13..b6fedb9162 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
Awareness of relevant operational intents
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
Awareness of relevant operational intents
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 @@ -99,6 +99,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0215 Implemented diff --git a/monitoring/uss_qualifier/suites/uspace/required_services.md b/monitoring/uss_qualifier/suites/uspace/required_services.md index 484d64e016..385a0ad6ad 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
Awareness of relevant operational intents
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
Awareness of relevant operational intents
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 @@ -534,6 +534,11 @@ Implemented ASTM SCD DSS: Subscription Synchronization + + DSS0210,A2-7-2,7 + Implemented + ASTM SCD DSS: Interfaces authentication + DSS0215 Implemented