Skip to content

Commit

Permalink
feat: add optional validation of input doc
Browse files Browse the repository at this point in the history
Signed-off-by: Daniel Bluhm <[email protected]>
  • Loading branch information
dbluhm committed Oct 20, 2023
1 parent 7548240 commit c6aece8
Show file tree
Hide file tree
Showing 7 changed files with 295 additions and 10 deletions.
15 changes: 15 additions & 0 deletions did_peer_4/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from hashlib import sha256

from .doc_visitor import DocVisitor
from .valid import validate_input_document

# Regex patterns
BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
Expand Down Expand Up @@ -56,8 +57,11 @@ def _hash_encoded_doc(encoded_doc: str) -> str:

def encode(
document: Dict[str, Any],
validate: bool = True,
) -> str:
"""Encode an input document into a did:peer:4."""
if validate:
document = dict(validate_input_document(document))
encoded_doc = _encode_doc(document)
hashed = _hash_encoded_doc(encoded_doc)
return f"did:peer:4{hashed}:{encoded_doc}"
Expand Down Expand Up @@ -156,3 +160,14 @@ def resolve_short_from_doc(
raise ValueError("Document does not match DID")

return resolve_short(long)


__all__ = [
"encode",
"encode_short",
"decode",
"resolve",
"resolve_short",
"resolve_short_from_doc",
"validate_input_document",
]
78 changes: 78 additions & 0 deletions did_peer_4/valid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"""Validate input documents."""
from typing import Any, Mapping


def resources(document: Mapping[str, Any]):
"""Yield all resources in a document, skipping references."""
keys = (
"verificationMethod",
"authentication",
"assertionMethod",
"keyAgreement",
"capabilityDelegation",
"capabilityInvocation",
"service",
)
for key in keys:
if key in document:
if not isinstance(document[key], list):
raise ValueError(f"{key} must be a list")

for index, resource in enumerate(document[key]):
if isinstance(resource, dict):
yield key, index, resource


def validate_input_document(document: Mapping[str, Any]) -> Mapping[str, Any]:
"""Validate did:peer:4 input document.
This validation is deliberately superficial. It is intended to catch mistakes
in the input document that would cause the peer DID to be invalid. It is not
intended to validate the contents of the document, which is left to the caller
after resolution.
The following checks are performed:
- The document must be a Mapping.
- The document must not be empty.
- The document must not contain an id.
- If present, alsoKnownAs must be a list.
- verificationMethod, authentication, assertionMethod, keyAgreement,
capabilityDelegation, capabilityInvocation, and service must be lists, if
present.
- All resources (verification methods, embedded verification methods,
services) must have an id.
- All resource ids must be strings.
- All resource ids must be relative.
- All resources must have a type.
"""
if not isinstance(document, Mapping):
raise ValueError("document must be a Mapping")

if not document:
raise ValueError("document must not be empty")

if "id" in document:
raise ValueError("id must not be present in input document")

if "alsoKnownAs" in document:
if not isinstance(document["alsoKnownAs"], list):
raise ValueError("alsoKnownAs must be a list")

for key, index, resource in resources(document):
if "id" not in resource:
raise ValueError(f"{key}[{index}]: resource must have an id")

ident = resource["id"]
if not isinstance(ident, str):
raise ValueError("r{key}[{index}]: esource id must be a string")

if not ident.startswith("#"):
raise ValueError(
"A{key}[{index}]: ll document resource ids must be relative"
)

if "type" not in resource:
raise ValueError("r{key}[{index}]: esource must have a type")

return document
121 changes: 120 additions & 1 deletion pdm.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,14 @@ dev = [
"black>=23.7.0",
"ruff>=0.0.285",
"pre-commit>=3.3.3",
"pytest-cov>=4.1.0",
]

[tool.coverage.report]
exclude_lines = [
"pragma: no cover",
"@abstract"
]
precision = 2
skip_covered = true
show_missing = true
11 changes: 11 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Tests for did:peer:4."""
from pathlib import Path


def examples():
"""Load json from examples directory and return generator over examples."""
examples_dir = Path(__file__).parent / "examples"
yield from examples_dir.glob("*.json")


EXAMPLES = list(examples())
10 changes: 1 addition & 9 deletions tests/test_readme_examples.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,9 @@
import json
import pytest
from pathlib import Path

from did_peer_4 import encode, long_to_short, resolve, resolve_short


def examples():
"""Load json from examples directory and return generator over examples."""
examples_dir = Path(__file__).parent / "examples"
yield from examples_dir.glob("*.json")


EXAMPLES = list(examples())
from . import EXAMPLES


def print_example(
Expand Down
Loading

0 comments on commit c6aece8

Please sign in to comment.