Establish certifiable Voting provider boundary

This commit is contained in:
2026-08-04 14:01:26 +02:00
parent 0168c5ecd5
commit 8cfd6bfd48
8 changed files with 371 additions and 2 deletions
+38
View File
@@ -21,8 +21,10 @@ from govoplan_core.core.voting import (
VotingBallotCreateCommand,
VotingBallotRef,
VotingCastCommand,
VotingCapabilityError,
VotingReceipt,
VotingResult,
require_voting_provider_assurance,
voting_provider_capability,
)
from govoplan_voting.backend.db.models import (
@@ -265,6 +267,16 @@ class SqlVotingBallots:
raise VotingStoreError(
"The selected Voting assurance profile requires an available external provider."
)
try:
declaration = require_voting_provider_assurance(
provider,
provider_id=provider_id,
assurance_profile=profile,
at=_now(),
)
except VotingCapabilityError as exc:
raise VotingStoreError(str(exc)) from exc
payload["provider_assurance"] = declaration.to_dict()
definition_hash, electorate_hash = _frozen_hashes(payload)
payload["definition_sha256"] = definition_hash
payload["electorate_sha256"] = electorate_hash
@@ -343,6 +355,7 @@ class SqlVotingBallots:
"electorate_sha256": electorate_hash,
"provider_id": payload.get("provider_id"),
"provider_ballot_ref": payload.get("provider_ballot_ref"),
"provider_assurance": payload.get("provider_assurance"),
"provider_evidence": [dict(item) for item in provider_evidence],
},
)
@@ -403,6 +416,7 @@ class SqlVotingBallots:
raise VotingStoreError(
"This Voting provider does not expose an interactive cast capability."
)
_require_pinned_provider_assurance(current.payload, provider)
try:
receipt = provider.cast_ballot(
typed_session,
@@ -863,6 +877,7 @@ class SqlVotingBallots:
provider = _capability(self._registry, voting_provider_capability(provider_id))
if not isinstance(provider, ExternalVotingProvider):
raise VotingStoreError(f"Voting provider is unavailable: {provider_id}.")
_require_pinned_provider_assurance(current.payload, provider)
electorate = list(current.payload["electorate"])
try:
result = provider.finalize_ballot(
@@ -944,6 +959,7 @@ def _payload_from_command(command: VotingBallotCreateCommand) -> dict[str, Any]:
"closes_at": _datetime_text(command.closes_at),
"provider_id": _optional_text(command.provider_id),
"provider_ballot_ref": _optional_text(command.provider_ballot_ref),
"provider_assurance": None,
"metadata": dict(command.metadata),
"definition_sha256": None,
"electorate_sha256": None,
@@ -1029,6 +1045,28 @@ def _validate_window(payload: Mapping[str, Any]) -> None:
raise VotingStoreError("Voting ballot has already reached its close time.")
def _require_pinned_provider_assurance(
payload: Mapping[str, Any],
provider: object,
) -> None:
provider_id = str(payload.get("provider_id") or "").strip()
assurance_profile = str(payload.get("assurance_profile") or "").strip()
try:
current = require_voting_provider_assurance(
provider,
provider_id=provider_id,
assurance_profile=assurance_profile,
at=_now(),
)
except VotingCapabilityError as exc:
raise VotingStoreError(str(exc)) from exc
pinned = payload.get("provider_assurance")
if not isinstance(pinned, Mapping) or dict(pinned) != current.to_dict():
raise VotingStoreError(
"Voting provider assurance changed after the ballot was frozen."
)
def _frozen_hashes(payload: Mapping[str, Any]) -> tuple[str, str]:
electorate = list(payload.get("electorate") or [])
definition = {