diff --git a/ooniapi/services/ooniprobe/pyproject.toml b/ooniapi/services/ooniprobe/pyproject.toml index 1d87bd2a..2b34ab17 100644 --- a/ooniapi/services/ooniprobe/pyproject.toml +++ b/ooniapi/services/ooniprobe/pyproject.toml @@ -30,7 +30,7 @@ dependencies = [ "boto3 ~= 1.39.3", "boto3-stubs[s3] ~= 1.39.3", "mypy-boto3-s3 ~= 1.39.5", - "ooniauth-py==0.2.0" + "ooniauth-py==0.2.1" ] readme = "README.md" diff --git a/ooniapi/services/ooniprobe/src/ooniprobe/dependencies.py b/ooniapi/services/ooniprobe/src/ooniprobe/dependencies.py index 913fbc52..b3cde8a1 100644 --- a/ooniapi/services/ooniprobe/src/ooniprobe/dependencies.py +++ b/ooniapi/services/ooniprobe/src/ooniprobe/dependencies.py @@ -64,8 +64,8 @@ class Policy(BaseModel): age: Tuple[int, int] = Field( description="Inclusive lower/upper bounds for the probe age accepted by this rule." ) - measurement_count: Tuple[int, int] = Field( - description="Inclusive lower/upper bounds for the probe measurement count accepted by this rule." + min_measurement_count: int = Field( + description="Minimum lower bound for the probe measurement count accepted by this rule." ) class Match(BaseModel): diff --git a/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py b/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py index 5d72ad48..f531cc6e 100644 --- a/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py +++ b/ooniapi/services/ooniprobe/src/ooniprobe/routers/v1/probe_services.py @@ -790,7 +790,13 @@ def _anonc_exc_to_str(error: ProtocolError | CredentialError | DeserializationFa """ returns a short error string depending on the error type """ - type_to_str = { + type_to_str: dict[ + type[ + ProtocolError | + CredentialError | + DeserializationFailed + ], + str] = { ProtocolError: "protocol_error", DeserializationFailed: "deserialization_failed", CredentialError: "credential_error", @@ -1111,8 +1117,12 @@ def _verify_submit( ) return VerificationStatus.UNVERIFIED, "invalid_protocol_version", None - # Get the limits in age range and measurement count for this request - age_range, count_range = get_ranges_from_policy(manifest.manifest.submission_policy, submit_request.content['probe_cc'], submit_request.content['probe_asn']) + # Get the age range and minimum measurement count for this request + age_range, min_msm_count = get_ranges_from_policy( + manifest.manifest.submission_policy, + submit_request.content["probe_cc"], + submit_request.content["probe_asn"], + ) # Run verification try: @@ -1124,8 +1134,8 @@ def _verify_submit( submit_request.zkp_request, submit_request.content["probe_cc"], submit_request.content["probe_asn"], - list(age_range), - list(count_range), + age_range, + min_msm_count, ) return (VerificationStatus.VERIFIED, None, submit_response) except (DeserializationFailed, ProtocolError, CredentialError) as e: @@ -1141,14 +1151,14 @@ def _parse_version_tuple(version: str) -> tuple[int, ...]: def get_ranges_from_policy( policy: List[PolicyEntry], probe_cc: str, probe_asn: str -) -> Tuple[Tuple[int, int], Tuple[int, int]]: +) -> Tuple[Tuple[int, int], int]: """ - Gets the age and measurement count ranges from the specified policy. + Gets the age range and minimum measurement count from the specified policy. Matching order: first match in the list wins (highest priority first). - returns: - age_range, msm_range + Returns: + age_range, min_msm_count """ for item in policy: @@ -1159,7 +1169,7 @@ def get_ranges_from_policy( asn_ok = match_asn == "*" or match_asn == probe_asn if cc_ok and asn_ok: - return item.policy.age, item.policy.measurement_count + return item.policy.age, item.policy.min_measurement_count raise ValueError( f"No matching submission_policy entry for probe_cc={probe_cc} probe_asn={probe_asn}" diff --git a/ooniapi/services/ooniprobe/tests/conftest.py b/ooniapi/services/ooniprobe/tests/conftest.py index e5a7aeb4..85625d40 100644 --- a/ooniapi/services/ooniprobe/tests/conftest.py +++ b/ooniapi/services/ooniprobe/tests/conftest.py @@ -118,7 +118,7 @@ def get_manifest_mock(): "match": {"probe_cc": "*", "probe_asn": "*"}, "policy": { "age": [2461110, 2826140], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, }, } ], diff --git a/ooniapi/services/ooniprobe/tests/test_anoncred.py b/ooniapi/services/ooniprobe/tests/test_anoncred.py index 02d56021..e0b52361 100644 --- a/ooniapi/services/ooniprobe/tests/test_anoncred.py +++ b/ooniapi/services/ooniprobe/tests/test_anoncred.py @@ -239,42 +239,42 @@ def test_get_ranges_from_policy_match_precedence(): policy = [ PolicyEntry( match=Match(probe_asn="AS15704", probe_cc="ES"), - policy=Policy(age=(9, 10), measurement_count=(90, 100)), + policy=Policy(age=(9, 10), min_measurement_count=90), ), PolicyEntry( match=Match(probe_asn="*", probe_cc="ES"), - policy=Policy(age=(7, 8), measurement_count=(70, 80)), + policy=Policy(age=(7, 8), min_measurement_count=70), ), PolicyEntry( match=Match(probe_asn="AS15704", probe_cc="*"), - policy=Policy(age=(5, 6), measurement_count=(50, 60)), + policy=Policy(age=(5, 6), min_measurement_count=50), ), PolicyEntry( match=Match(probe_asn="*", probe_cc="*"), - policy=Policy(age=(3, 4), measurement_count=(30, 40)), + policy=Policy(age=(3, 4), min_measurement_count=30), ), ] - age_range, msm_range = get_ranges_from_policy(policy, "ES", "AS15704") + age_range, msm_min = get_ranges_from_policy(policy, "ES", "AS15704") assert age_range == (9, 10) - assert msm_range == (90, 100) + assert msm_min == 90 - age_range, msm_range = get_ranges_from_policy(policy, "ES", "AS99999") + age_range, msm_min = get_ranges_from_policy(policy, "ES", "AS99999") assert age_range == (7, 8) - assert msm_range == (70, 80) + assert msm_min == 70 - age_range, msm_range = get_ranges_from_policy(policy, "IT", "AS15704") + age_range, msm_min = get_ranges_from_policy(policy, "IT", "AS15704") assert age_range == (5, 6) - assert msm_range == (50, 60) + assert msm_min == 50 - age_range, msm_range = get_ranges_from_policy(policy, "IT", "AS99999") + age_range, msm_min = get_ranges_from_policy(policy, "IT", "AS99999") assert age_range == (3, 4) - assert msm_range == (30, 40) + assert msm_min == 30 no_catchall_policy = [ PolicyEntry( match=Match(probe_asn="AS15704", probe_cc="ES"), - policy=Policy(age=(9, 10), measurement_count=(90, 100)), + policy=Policy(age=(9, 10), min_measurement_count=90), ) ] with pytest.raises(ValueError, match="No matching submission_policy entry"): @@ -285,37 +285,39 @@ def test_get_ranges_from_policy_uses_wildcard_match(): policy = [ PolicyEntry( match=Match(probe_asn="*", probe_cc="*"), - policy=Policy(age=(11, 12), measurement_count=(110, 120)), + policy=Policy(age=(11, 12), min_measurement_count=110), ) ] - age_range, msm_range = get_ranges_from_policy(policy, "BR", "AS28573") + age_range, msm_min = get_ranges_from_policy(policy, "BR", "AS28573") assert age_range == (11, 12) - assert msm_range == (110, 120) + assert msm_min == 110 def test_get_ranges_from_policy_requires_matching_entry(): with pytest.raises(ValueError, match="No matching submission_policy entry"): get_ranges_from_policy([], "FR", "AS3215") -def test_policy_entry_requires_both_ranges(): +def test_policy_requires_age_and_min_measurement_count(): with pytest.raises(ValidationError): Policy.model_validate({"age": [21, 22]}) + with pytest.raises(ValidationError): + Policy.model_validate({"min_measurement_count": 1}) def test_get_ranges_from_policy_first_match_wins(): policy = [ PolicyEntry( match=Match(probe_asn="*", probe_cc="*"), - policy=Policy(age=(1, 1), measurement_count=(1, 1)), + policy=Policy(age=(1, 1), min_measurement_count=1), ), PolicyEntry( match=Match(probe_asn="AS1234", probe_cc="IT"), - policy=Policy(age=(9, 9), measurement_count=(9, 9)), + policy=Policy(age=(9, 9), min_measurement_count=9), ), ] - age_range, msm_range = get_ranges_from_policy(policy, "IT", "AS1234") + age_range, msm_min = get_ranges_from_policy(policy, "IT", "AS1234") assert age_range == (1, 1) - assert msm_range == (1, 1) + assert msm_min == 1 def _manifest_from_payload(payload): @@ -337,7 +339,7 @@ def test_manifest_parsing_preserves_important_fields(): "match": {"probe_cc": "*", "probe_asn": "*"}, "policy": { "age": [2461110, 2826140], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, }, } ] @@ -350,7 +352,7 @@ def test_manifest_parsing_preserves_important_fields(): assert entry.match.probe_cc == "*" assert entry.match.probe_asn == "*" assert entry.policy.age == (2461110, 2826140) - assert entry.policy.measurement_count == (0, 10000000) + assert entry.policy.min_measurement_count == 0 def test_manifest_rejects_ranges_with_invalid_length(): @@ -362,7 +364,7 @@ def test_manifest_rejects_ranges_with_invalid_length(): "match": {"probe_cc": "*", "probe_asn": "*"}, "policy": { "age": [2461110], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, }, } ] @@ -374,7 +376,10 @@ def test_manifest_rejects_ranges_with_invalid_length(): "submission_policy": [ { "match": {"probe_cc": "*", "probe_asn": "*"}, - "policy": {"age": [2461110, 2826140], "measurement_count": [0]}, + "policy": { + "age": [2461110, 2826140], + "min_measurement_count": [0, 10000000], + }, } ] } @@ -390,7 +395,7 @@ def test_manifest_requires_probe_cc_and_probe_asn(): "match": {"probe_cc": "*"}, "policy": { "age": [2461110, 2826140], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, }, } ] @@ -404,7 +409,7 @@ def test_manifest_requires_probe_cc_and_probe_asn(): "match": {"probe_asn": "*"}, "policy": { "age": [2461110, 2826140], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, }, } ] @@ -424,7 +429,7 @@ def test_manifest_rejects_missing_or_bad_types_for_policy_and_match(): { "policy": { "age": [2461110, 2826140], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, } } ] @@ -440,7 +445,7 @@ def test_manifest_rejects_missing_or_bad_types_for_policy_and_match(): "match": "not-a-dict", "policy": { "age": [2461110, 2826140], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, }, } ] @@ -459,7 +464,7 @@ def test_manifest_requires_catch_all_rule(): "match": {"probe_cc": "IT", "probe_asn": "AS1234"}, "policy": { "age": [2461110, 2826140], - "measurement_count": [0, 10000000], + "min_measurement_count": 0, }, } ] @@ -472,11 +477,11 @@ def test_manifest_requires_catch_all_rule(): @pytest.mark.asyncio async def test_credential_update(client, client_with_original_manifest, second_manifest): - (user, manifest, _) = client_with_original_manifest + (user, manifest_version, _) = client_with_original_manifest new_manifest = getj(client, "/api/v1/manifest") user.set_public_params(new_manifest["manifest"]["public_parameters"]) result = postj(client, "/api/v1/update_credential", json=dict( - old_manifest_version = manifest, + old_manifest_version = manifest_version, manifest_version = new_manifest['meta']['version'], update_request = user.make_credential_update_request() )) diff --git a/ooniapi/services/ooniprobe/tests/test_main.py b/ooniapi/services/ooniprobe/tests/test_main.py index adb8f850..065d1602 100644 --- a/ooniapi/services/ooniprobe/tests/test_main.py +++ b/ooniapi/services/ooniprobe/tests/test_main.py @@ -21,7 +21,7 @@ def fake_get_manifest(s3, bucket, key): submission_policy=[ PolicyEntry( match=Match(probe_cc="*", probe_asn="*"), - policy=Policy(age=(2461110, 2826140), measurement_count=(0, 10000000)), + policy=Policy(age=(2461110, 2826140), min_measurement_count=0), ) ], public_parameters="public parameters" diff --git a/ooniapi/services/ooniprobe/tests/utils.py b/ooniapi/services/ooniprobe/tests/utils.py index 04248bf6..3322edec 100644 --- a/ooniapi/services/ooniprobe/tests/utils.py +++ b/ooniapi/services/ooniprobe/tests/utils.py @@ -45,7 +45,7 @@ def make_submit_request(user: UserState, probe_cc: str, probe_asn: str): probe_cc, probe_asn, (2461110, 2826140), - (0, 10000000), + 0, )