Skip to content

Commit 23a5f57

Browse files
authored
✨ Validate Election Manifest (#126)
* ✨ Validate Election Manifest - Add `election/validate/description` to validate election manifests and descriptions - Add postman tests - ♻️ Update to use lru cache * 🆙 Upgrade electionguard
1 parent 64ecc9f commit 23a5f57

File tree

7 files changed

+178
-20
lines changed

7 files changed

+178
-20
lines changed

app/api/v1/mediator/election.py

Lines changed: 50 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,22 @@
11
from os.path import realpath, join
2-
from typing import Any
2+
from typing import Any, Optional
33
from electionguard.election import (
44
ElectionDescription,
55
ElectionConstants,
66
make_ciphertext_election_context,
77
)
88
from electionguard.group import ElementModP
9+
from electionguard.schema import validate_json_schema
910
from electionguard.serializable import read_json_object, write_json_object
10-
from fastapi import APIRouter, Body
1111

12-
from ..models import ElectionContextRequest
12+
from fastapi import APIRouter, Body, Depends
13+
14+
from app.core.schema import get_description_schema
15+
from ..models import (
16+
ElectionContextRequest,
17+
ValidationResponse,
18+
ValidateElectionDescriptionRequest,
19+
)
1320
from ..tags import CONFIGURE_ELECTION
1421

1522
router = APIRouter()
@@ -47,3 +54,43 @@ def build_election_context(request: ElectionContextRequest = Body(...)) -> Any:
4754
)
4855

4956
return write_json_object(context)
57+
58+
59+
@router.post("/validate/description", tags=[CONFIGURE_ELECTION])
60+
def validate_election_description(
61+
request: ValidateElectionDescriptionRequest = Body(...),
62+
schema: Any = Depends(get_description_schema),
63+
) -> Any:
64+
"""
65+
Validate an Election description or manifest for a given election
66+
"""
67+
68+
success = True
69+
message = "Election description successfully validated"
70+
details = ""
71+
72+
# Check schema
73+
schema = request.schema_override if request.schema_override else schema
74+
(schema_success, error_details) = validate_json_schema(request.description, schema)
75+
if not schema_success:
76+
success = schema_success
77+
message = "Election description did not match schema"
78+
details = error_details
79+
80+
# Check object parse
81+
description: Optional[ElectionDescription] = None
82+
if success:
83+
try:
84+
description = ElectionDescription.from_json_object(request.description)
85+
except Exception: # pylint: disable=broad-except
86+
success = False
87+
message = "Election description could not be read from JSON"
88+
89+
if success:
90+
if description:
91+
valid_success = description.is_valid()
92+
if not valid_success:
93+
message = "Election description was not valid well formed data"
94+
95+
# Check
96+
return ValidationResponse(success=success, message=message, details=details)

app/api/v1/models/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
from .key import *
66
from .tally import *
77
from .tracker import *
8+
from .validation import *

app/api/v1/models/election.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
from typing import Any
2+
23
from .base import Base
4+
from .validation import BaseValidationRequest
5+
36

4-
__all__ = ["CiphertextElectionContext", "ElectionContextRequest", "ElectionDescription"]
7+
__all__ = [
8+
"CiphertextElectionContext",
9+
"ElectionContextRequest",
10+
"ElectionDescription",
11+
"ValidateElectionDescriptionRequest",
12+
]
513

614
ElectionDescription = Any
715

@@ -17,3 +25,11 @@ class ElectionContextRequest(Base):
1725
elgamal_public_key: str
1826
number_of_guardians: int
1927
quorum: int
28+
29+
30+
class ValidateElectionDescriptionRequest(BaseValidationRequest):
31+
"""
32+
A request to validate an Election Description
33+
"""
34+
35+
description: ElectionDescription

app/api/v1/models/validation.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
from typing import Any, Optional
2+
from .base import Base
3+
4+
__all__ = ["BaseValidationRequest", "ValidationResponse", "Schema"]
5+
6+
Schema = Any
7+
8+
9+
class BaseValidationRequest(Base):
10+
"""Base validation request"""
11+
12+
schema_override: Optional[Schema] = None
13+
14+
15+
class ValidationResponse(Base):
16+
"""Response for validating models"""
17+
18+
success: bool
19+
message: str
20+
details: str = ""

app/core/schema.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
from functools import lru_cache
2+
from typing import Any
3+
from electionguard.schema import get_election_description_schema
4+
5+
__all__ = ["get_description_schema"]
6+
7+
8+
@lru_cache
9+
def get_description_schema() -> Any:
10+
return get_election_description_schema()

poetry.lock

Lines changed: 38 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/postman/ElectionGuard Web Api.postman_collection.json

Lines changed: 42 additions & 12 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)