Establish certifiable Voting provider boundary
This commit is contained in:
@@ -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 = {
|
||||
|
||||
Reference in New Issue
Block a user