Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ooniapi/services/ooniprobe/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions ooniapi/services/ooniprobe/src/ooniprobe/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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:
Expand All @@ -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}"
Expand Down
2 changes: 1 addition & 1 deletion ooniapi/services/ooniprobe/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
}
],
Expand Down
69 changes: 37 additions & 32 deletions ooniapi/services/ooniprobe/tests/test_anoncred.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"):
Expand All @@ -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):
Expand All @@ -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,
},
}
]
Expand All @@ -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():
Expand All @@ -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,
},
}
]
Expand All @@ -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],
},
}
]
}
Expand All @@ -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,
},
}
]
Expand All @@ -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,
},
}
]
Expand All @@ -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,
}
}
]
Expand All @@ -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,
},
}
]
Expand All @@ -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,
},
}
]
Expand All @@ -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()
))
Expand Down
2 changes: 1 addition & 1 deletion ooniapi/services/ooniprobe/tests/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
2 changes: 1 addition & 1 deletion ooniapi/services/ooniprobe/tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)


Expand Down
Loading