class VerificationMethodOptions(Option): """Container for validation options for VerificationMethods.""" allow_type_list = 0, Schema( {"type": All(Switch(str, [str]), _option_allow_type_list)}, extra=ALLOW_EXTRA) allow_missing_controller = 1, Schema( All( {Required("id"): All(str, DIDUrl.validate)}, _option_allow_missing_controller, ), extra=ALLOW_EXTRA, ) allow_controller_list = 2, Schema( {"controller": All(Switch(str, [str]), _option_allow_controller_list)}, extra=ALLOW_EXTRA, ) @property def priority(self): """Return the priority for this option.""" return self.value[0] @property def schema(self): """Return the schema defined by option.""" return self.value[1] @classmethod def apply(cls, value, options: Set["VerificationMethodOptions"]): """Apply options to value""" return All(*cls.schemas_in_application_order(options))(value)
def _verification_methods(schema): return { "verificationMethod": [schema], **{ key: [Switch(str, schema)] for key in ( "authentication", "assertionMethod", "keyAgreement", "capabilityInvocation", "capabilityDelegation", ) }, }
class Service: """Representation of DID Document Services.""" _validator = Schema( { "id": All(str, DIDUrl.validate), "type": str, "serviceEndpoint": Switch(DIDUrl.validate, Url()), }, extra=ALLOW_EXTRA, required=True, ) def __init__(self, id_: DIDUrl, type_: str, endpoint: str, **extra): """Initialize Service.""" self._id = id_ self._type = type_ self._endpoint = endpoint self._extra = extra @property def id(self): """Return id.""" return self._id @property def type(self): """Return type.""" return self._type @property def endpoint(self): """Return endpoint.""" return self._endpoint @property def extra(self): """Return extra.""" return self._extra def serialize(self): """Return serialized representation of Service.""" return { "id": str(self.id), "type": self.type, "serviceEndpoint": self.endpoint, **self.extra, } @classmethod @wrap_validation_error(ServiceValidationError, message="Failed to validate service") def validate(cls, value: dict): """Validate object against service.""" return cls._validator(value) @classmethod @wrap_validation_error( ServiceValidationError, message="Failed to deserialize service" ) def deserialize(cls, value: dict): """Deserialize into Service.""" value = cls.validate(value) deserializer = Schema( { Into("id", "id_"): DIDUrl.parse, Into("type", "type_"): str, Into("serviceEndpoint", "endpoint"): str, }, extra=ALLOW_EXTRA, ) value = deserializer(value) return cls(**value)
class DIDDocument: """Representation of DID Document.""" properties = Properties(extra=ALLOW_EXTRA) def __init__(self, id: Union[str, DID], context: List[Any], *, also_known_as: List[str] = None, controller: List[str] = None, verification_method: List[VerificationMethod] = None, authentication: VerificationRelationship = None, assertion_method: VerificationRelationship = None, key_agreement: VerificationRelationship = None, capability_invocation: VerificationRelationship = None, capability_delegation: VerificationRelationship = None, service: List[Service] = None, **extra): """Create DIDDocument.""" self._id = id self._context = context self._also_known_as = also_known_as self._controller = controller self._verification_method = verification_method self._authentication = authentication self._assertion_method = assertion_method self._key_agreement = key_agreement self._capability_invocation = capability_invocation self._capability_delegation = capability_delegation self._service = service self.extra = extra self._index = {} self._index_resources() def _index_resources(self): """Index resources by ID. IDs are not guaranteed to be unique within the document. The first instance is stored in the index and subsequent id collisions are checked against the original. If they do not match, an error will be thrown. """ def _indexer(item): if not item: # Attribute isn't set return if isinstance(item, DIDUrl): # We don't index references return if isinstance(item, list): for subitem in item: _indexer(subitem) return if isinstance(item, VerificationRelationship): for subitem in item.items: _indexer(subitem) return assert isinstance(item, (VerificationMethod, Service)) if item.id in self._index and item != self._index[item.id]: raise IdentifiedResourceMismatch( "ID {} already found in Index and Items do not match". format(item.id)) self._index[item.id] = item for item in ( self.verification_method, self.authentication, self.assertion_method, self.key_agreement, self.capability_invocation, self.capability_delegation, self.service, ): _indexer(item) @property @properties.add( data_key="@context", required=True, validate=Switch(Url(), [Url()], dict, [dict]), serialize=unwrap_if_list_of_one, deserialize=single_to_list, ) def context(self): """Return context.""" return self._context @property @properties.add( required=True, validate=All(str, DID.validate), serialize=Coerce(str), deserialize=Coerce(DID), ) def id(self): """Return id.""" return self._id @property @properties.add(data_key="alsoKnownAs", validate=[str]) def also_known_as(self): """Return also_known_as.""" return self._also_known_as @property @properties.add( validate=Switch(All(str, DID.validate), [DID.validate]), serialize=All([Coerce(str)], unwrap_if_list_of_one), deserialize=All(single_to_list, [Coerce(DID)]), ) def controller(self): """Return controller.""" return self._controller @property @properties.add( data_key="verificationMethod", validate=[VerificationMethod.validate], serialize=[serialize], deserialize=[VerificationMethod.deserialize], ) def verification_method(self): """Return verification_method.""" return self._verification_method @property @properties.add( validate=VerificationRelationship.validate, serialize=serialize, deserialize=VerificationRelationship.deserialize, ) def authentication(self): """Return authentication.""" return self._authentication @property @properties.add( data_key="assertionMethod", validate=VerificationRelationship.validate, serialize=serialize, deserialize=VerificationRelationship.deserialize, ) def assertion_method(self): """Return assertion_method.""" return self._assertion_method @property @properties.add( data_key="keyAgreement", validate=VerificationRelationship.validate, serialize=serialize, deserialize=VerificationRelationship.deserialize, ) def key_agreement(self): """Return key_agreement.""" return self._key_agreement @property @properties.add( data_key="capabilityInvocation", validate=VerificationRelationship.validate, serialize=serialize, deserialize=VerificationRelationship.deserialize, ) def capability_invocation(self): """Return capability_invocation.""" return self._capability_invocation @property @properties.add( data_key="capabilityDelegation", validate=VerificationRelationship.validate, serialize=serialize, deserialize=VerificationRelationship.deserialize, ) def capability_delegation(self): """Return capability_delegation.""" return self._capability_delegation @property @properties.add( validate=[Service.validate], serialize=[serialize], deserialize=[Service.deserialize], ) def service(self): """Return service.""" return self._service def dereference(self, reference: Union[str, DIDUrl]): """Dereference a DID URL to a document resource.""" if isinstance(reference, str): reference = DIDUrl.parse(reference) if reference not in self._index: raise ResourceIDNotFound( "ID {} not found in document".format(reference)) return self._index[reference] @classmethod @wrap_validation_error(DIDDocumentValidationError, message="Failed to validate DID Document") def validate(cls, value): """Validate against expected schema.""" return cls.properties.validate(value) @wrap_validation_error(DIDDocumentError, message="Failed to serialize DID Document") def serialize(self): """Serialize DID Document.""" value = self.properties.serialize(self) return {**value, **self.extra} @classmethod @wrap_validation_error(DIDDocumentValidationError, message="Failed to deserialize DID Document") def deserialize(cls, value: dict, options: Set[Option] = None): """Deserialize DID Document.""" if options: value = DIDDocumentOption.apply(value, options) value = cls.validate(value) value = cls.properties.deserialize(value) return cls(**value)
class DIDDocumentBuilder: """Builder for constructing DID Documents programmatically.""" DEFAULT_CONTEXT = ["https://www.w3.org/ns/did/v1"] @validate_init(id_=Switch(All(str, Coerce(DID)), DID)) def __init__(self, id_: DID, context: List[str] = None, *, also_known_as: List[str] = None, controller: List[str] = None): """Initliaze builder.""" self.id = id_ self.context = context or self.DEFAULT_CONTEXT self.also_known_as = also_known_as self.controller = controller self.verification_methods = VerificationMethodBuilder(self.id) self.authentication = RelationshipBuilder(self.id, "auth") self.assertion_method = RelationshipBuilder(self.id, "assert") self.key_agreement = RelationshipBuilder(self.id, "key-agreement") self.capability_invocation = RelationshipBuilder( self.id, "capability-invocation") self.capability_delegation = RelationshipBuilder( self.id, "capability-delegation") self.services = ServiceBuilder(self.id) self.extra = {} @classmethod def from_doc(cls, doc: DIDDocument) -> "DIDDocumentBuilder": """Create a Builder from an existing DIDDocument.""" builder = cls( id_=doc.did, context=doc.context, also_known_as=doc.also_known_as, controller=doc.controller, ) builder.verification_methods = VerificationMethodBuilder( doc.did, methods=doc.verification_method) builder.authentication = RelationshipBuilder( doc.did, "auth", methods=doc.authentication) builder.assertion_method = RelationshipBuilder( doc.did, "assert", methods=doc.assertion_method) builder.key_agreement = RelationshipBuilder(doc.did, "key-agreement", methods=doc.key_agreement) builder.capability_invocation = RelationshipBuilder( doc.did, "capability-invocation", methods=doc.capability_invocation) builder.capability_delegation = RelationshipBuilder( doc.did, "capability-delegation", methods=doc.capability_delegation) builder.services = ServiceBuilder(doc.did, services=doc.service) return builder def build(self) -> DIDDocument: """Build document.""" return DIDDocument( id=str(self.id), context=self.context, also_known_as=self.also_known_as, controller=self.controller, verification_method=self.verification_methods.methods or None, authentication=VerificationRelationship( self.authentication.methods) or None, assertion_method=VerificationRelationship( self.assertion_method.methods) or None, key_agreement=VerificationRelationship(self.key_agreement.methods) or None, capability_invocation=VerificationRelationship( self.capability_invocation.methods) or None, capability_delegation=VerificationRelationship( self.capability_delegation.methods) or None, service=self.services.services or None, **self.extra)