use async_trait::async_trait;
use chrono::prelude::{DateTime, Utc};
#[cfg(feature = "http")]
use reqwest::{header, Client, StatusCode, Url};
use serde::{Deserialize, Serialize};
use serde_json;
use serde_json::Value;
use serde_urlencoded;
use std::collections::{BTreeMap, HashMap, HashSet};
use std::convert::TryFrom;
use crate::error::Error;
use crate::{
DIDMethod, DIDParameters, Document, PrimaryDIDURL, Resource, ServiceEndpoint,
VerificationMethod, VerificationMethodMap, VerificationRelationship, DIDURL,
};
use ssi_core::one_or_many::OneOrMany;
use ssi_json_ld::DID_RESOLUTION_V1_CONTEXT;
use ssi_jwk::JWK;
pub const TYPE_JSON: &str = "application/json";
pub const TYPE_LD_JSON: &str = "application/ld+json";
pub const TYPE_DID_JSON: &str = "application/did+json";
pub const TYPE_DID_LD_JSON: &str = "application/did+ld+json";
pub const TYPE_URL: &str = "text/url";
pub const ERROR_INVALID_DID: &str = "invalidDid";
pub const ERROR_INVALID_DID_URL: &str = "invalidDidUrl";
pub const ERROR_UNAUTHORIZED: &str = "unauthorized";
pub const ERROR_NOT_FOUND: &str = "notFound";
pub const ERROR_METHOD_NOT_SUPPORTED: &str = "methodNotSupported";
pub const ERROR_REPRESENTATION_NOT_SUPPORTED: &str = "representationNotSupported";
pub const TYPE_DID_RESOLUTION: &str =
"application/ld+json;profile=\"https://w3id.org/did-resolution\";charset=utf-8";
pub const MAX_CONTROLLERS: usize = 100;
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum Metadata {
String(String),
Map(HashMap<String, Metadata>),
List(Vec<Metadata>),
Boolean(bool),
Null,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ResolutionInputMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub accept: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub version_time: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub no_cache: Option<bool>,
#[serde(flatten)]
pub property_set: Option<HashMap<String, Metadata>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct ResolutionMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(flatten)]
pub property_set: Option<HashMap<String, Metadata>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct DocumentMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub created: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub updated: Option<DateTime<Utc>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deactivated: Option<bool>,
#[serde(flatten)]
pub property_set: Option<HashMap<String, Metadata>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct DereferencingInputMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub accept: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub service_type: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub follow_redirect: Option<bool>,
#[serde(flatten)]
pub property_set: Option<HashMap<String, Metadata>>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct DereferencingMetadata {
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub content_type: Option<String>,
#[serde(flatten)]
pub property_set: Option<HashMap<String, Metadata>>,
}
#[derive(Debug, Serialize, Clone, PartialEq, Eq)]
#[serde(untagged)]
#[allow(clippy::large_enum_variant)]
pub enum Content {
DIDDocument(Document),
URL(String),
Object(Resource),
Data(Vec<u8>),
Null,
}
#[cfg(feature = "http")]
fn get_first_context_uri(value: &Value) -> Option<&str> {
match value.get("@context")? {
Value::Array(ref vec) => vec.get(0)?.as_str(),
Value::String(ref string) => Some(string.as_str()),
_ => None,
}
}
impl Content {
pub fn into_vec(self) -> Result<Vec<u8>, Error> {
if let Content::Data(data) = self {
Ok(data)
} else {
Ok(serde_json::to_vec(&self)?)
}
}
}
impl From<Error> for DereferencingMetadata {
fn from(err: Error) -> Self {
DereferencingMetadata {
error: Some(err.to_string()),
..Default::default()
}
}
}
impl From<ResolutionMetadata> for DereferencingMetadata {
fn from(res_meta: ResolutionMetadata) -> Self {
Self {
error: res_meta.error,
content_type: res_meta.content_type,
property_set: res_meta.property_set,
}
}
}
impl From<DereferencingMetadata> for ResolutionMetadata {
fn from(deref_meta: DereferencingMetadata) -> Self {
Self {
error: deref_meta.error,
content_type: deref_meta.content_type,
property_set: deref_meta.property_set,
}
}
}
impl DereferencingMetadata {
pub fn from_error(err: &str) -> Self {
DereferencingMetadata {
error: Some(err.to_owned()),
..Default::default()
}
}
}
impl ResolutionMetadata {
pub fn from_error(err: &str) -> Self {
ResolutionMetadata {
error: Some(err.to_owned()),
..Default::default()
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(untagged)]
pub enum ContentMetadata {
DIDDocument(DocumentMetadata),
Other(HashMap<String, Metadata>),
}
impl Default for ContentMetadata {
fn default() -> Self {
ContentMetadata::Other(HashMap::new())
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct ResolutionResult {
#[serde(rename = "@context")]
#[serde(skip_serializing_if = "Option::is_none")]
pub context: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub did_document: Option<Document>,
#[serde(skip_serializing_if = "Option::is_none")]
pub did_resolution_metadata: Option<ResolutionMetadata>,
#[serde(skip_serializing_if = "Option::is_none")]
pub did_document_metadata: Option<DocumentMetadata>,
#[serde(flatten)]
pub property_set: Option<BTreeMap<String, Value>>,
}
impl Default for ResolutionResult {
fn default() -> Self {
Self {
context: Some(Value::String(DID_RESOLUTION_V1_CONTEXT.to_string())),
did_document: None,
did_resolution_metadata: None,
did_document_metadata: None,
property_set: None,
}
}
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
pub trait DIDResolver: Sync {
async fn resolve(
&self,
did: &str,
input_metadata: &ResolutionInputMetadata,
) -> (
ResolutionMetadata,
Option<Document>,
Option<DocumentMetadata>,
);
async fn resolve_representation(
&self,
did: &str,
input_metadata: &ResolutionInputMetadata,
) -> (ResolutionMetadata, Vec<u8>, Option<DocumentMetadata>) {
let (mut res_meta, doc, doc_meta) = self.resolve(did, input_metadata).await;
let doc_representation = match doc {
None => Vec::new(),
Some(doc) => match serde_json::to_vec_pretty(&doc) {
Ok(vec) => vec,
Err(err) => {
res_meta.error =
Some("Error serializing JSON: ".to_string() + &err.to_string());
Vec::new()
}
},
};
res_meta.content_type = Some(TYPE_DID_LD_JSON.to_string());
(res_meta, doc_representation, doc_meta)
}
async fn dereference(
&self,
_primary_did_url: &PrimaryDIDURL,
_did_url_dereferencing_input_metadata: &DereferencingInputMetadata,
) -> Option<(DereferencingMetadata, Content, ContentMetadata)> {
None
}
fn to_did_method(&self) -> Option<&dyn DIDMethod> {
None
}
}
pub async fn dereference(
resolver: &dyn DIDResolver,
did_url_str: &str,
did_url_dereferencing_input_metadata: &DereferencingInputMetadata,
) -> (DereferencingMetadata, Content, ContentMetadata) {
let did_url = match DIDURL::try_from(did_url_str.to_string()) {
Ok(did_url) => did_url,
Err(_error) => {
return (
DereferencingMetadata::from_error(ERROR_INVALID_DID_URL),
Content::Null,
ContentMetadata::default(),
);
}
};
let did_res_input_metadata: ResolutionInputMetadata = match did_url.query.as_ref() {
Some(query) => match serde_urlencoded::from_str(query) {
Ok(meta) => meta,
Err(error) => {
return (
DereferencingMetadata::from(Error::from(error)),
Content::Null,
ContentMetadata::default(),
);
}
},
None => ResolutionInputMetadata::default(),
};
let (did_doc_res_meta, did_doc_opt, did_doc_meta_opt) = resolver
.resolve(&did_url.did, &did_res_input_metadata)
.await;
if let Some(ref error) = did_doc_res_meta.error {
return (
DereferencingMetadata::from_error(error),
Content::Null,
ContentMetadata::default(),
);
}
let (did_doc, did_doc_meta) = match (did_doc_opt, did_doc_meta_opt) {
(Some(doc), Some(meta)) => (doc, meta),
_ => {
return (
DereferencingMetadata::from_error(ERROR_NOT_FOUND),
Content::Null,
ContentMetadata::default(),
);
}
};
let (primary_did_url, fragment) = did_url.remove_fragment();
let (deref_meta, content, content_meta) = dereference_primary_resource(
resolver,
&primary_did_url,
did_url_dereferencing_input_metadata,
&did_doc_res_meta,
did_doc,
&did_doc_meta,
)
.await;
if deref_meta.error.is_some() {
return (deref_meta, content, content_meta);
}
if let Some(fragment) = fragment {
return dereference_secondary_resource(
resolver,
primary_did_url,
fragment,
did_url_dereferencing_input_metadata,
deref_meta,
content,
content_meta,
)
.await;
}
(deref_meta, content, content_meta)
}
async fn dereference_primary_resource(
resolver: &dyn DIDResolver,
primary_did_url: &PrimaryDIDURL,
did_url_dereferencing_input_metadata: &DereferencingInputMetadata,
res_meta: &ResolutionMetadata,
did_doc: Document,
_did_doc_meta: &DocumentMetadata,
) -> (DereferencingMetadata, Content, ContentMetadata) {
let parameters: DIDParameters = match primary_did_url.query {
Some(ref query) => match serde_urlencoded::from_str(query) {
Ok(params) => params,
Err(err) => {
return (
DereferencingMetadata::from(Error::from(err)),
Content::Null,
ContentMetadata::default(),
);
}
},
None => Default::default(),
};
if let Some(ref service) = parameters.service {
let service = match did_doc.select_service(service) {
Some(service) => service,
None => {
return (
DereferencingMetadata::from_error("Service not found"),
Content::Null,
ContentMetadata::default(),
);
}
};
let input_service_endpoint_url = match &service.service_endpoint {
None => {
return (
DereferencingMetadata::from_error("Missing service endpoint"),
Content::Null,
ContentMetadata::default(),
);
}
Some(OneOrMany::One(ServiceEndpoint::URI(uri))) => uri,
Some(OneOrMany::One(ServiceEndpoint::Map(_))) => {
return (
DereferencingMetadata::from_error("serviceEndpoint map not supported"),
Content::Null,
ContentMetadata::default(),
);
}
Some(OneOrMany::Many(_)) => {
return (
DereferencingMetadata::from_error(
"Multiple serviceEndpoint properties not supported",
),
Content::Null,
ContentMetadata::default(),
);
}
};
let did_url = DIDURL::from(primary_did_url.clone());
let output_service_endpoint_url =
match construct_service_endpoint(&did_url, ¶meters, input_service_endpoint_url) {
Ok(url) => url,
Err(err) => {
return (
DereferencingMetadata::from_error(&format!(
"Unable to construct service endpoint: {}",
err
)),
Content::Null,
ContentMetadata::default(),
);
}
};
return (
DereferencingMetadata {
content_type: Some(TYPE_URL.to_string()),
..Default::default()
},
Content::URL(output_service_endpoint_url),
ContentMetadata::default(),
);
}
if primary_did_url.path.is_none() && primary_did_url.query.is_none() {
let deref_meta = DereferencingMetadata {
content_type: Some(TYPE_DID_LD_JSON.to_string()),
..DereferencingMetadata::from(res_meta.clone())
};
return (
deref_meta,
Content::DIDDocument(did_doc),
ContentMetadata::default(),
);
}
if primary_did_url.path.is_some() || primary_did_url.query.is_some() {
if let Some(result) = resolver
.dereference(primary_did_url, did_url_dereferencing_input_metadata)
.await
{
return result;
}
}
#[allow(clippy::let_and_return)]
let null_result = (
DereferencingMetadata::default(),
Content::Null,
ContentMetadata::default(),
);
null_result
}
async fn dereference_secondary_resource(
_resolver: &dyn DIDResolver,
primary_did_url: PrimaryDIDURL,
fragment: String,
_did_url_dereferencing_input_metadata: &DereferencingInputMetadata,
deref_meta: DereferencingMetadata,
content: Content,
content_meta: ContentMetadata,
) -> (DereferencingMetadata, Content, ContentMetadata) {
let content_type = deref_meta.content_type.as_deref();
match content {
Content::DIDDocument(ref doc)
if content_type == Some(TYPE_DID_LD_JSON) || content_type == Some(TYPE_DID_JSON) =>
{
let did_url = primary_did_url.with_fragment(fragment);
let object = match doc.select_object(&did_url) {
Ok(object) => object,
Err(error) => {
return (
DereferencingMetadata::from_error(&format!(
"Unable to find object in DID document: {}",
error
)),
Content::Null,
ContentMetadata::default(),
);
}
};
return (
DereferencingMetadata {
content_type: Some(String::from(if content_type == Some(TYPE_DID_LD_JSON) {
TYPE_LD_JSON
} else {
TYPE_JSON
})),
..Default::default()
},
Content::Object(object),
ContentMetadata::default(),
);
}
Content::URL(mut url) => {
if url.contains('#') {
return (
DereferencingMetadata::from_error("DID URL and input service endpoint URL MUST NOT both have a fragment component"),
Content::Null,
ContentMetadata::default()
);
}
url.push('#');
url.push_str(&fragment);
return (deref_meta, Content::URL(url), content_meta);
}
_ => {}
}
match content_type {
None => (
DereferencingMetadata::from_error("Resource missing content type"),
Content::Null,
ContentMetadata::default(),
),
Some(content_type) => (
DereferencingMetadata::from_error(&format!(
"Unsupported content type: {}",
content_type
)),
Content::Null,
ContentMetadata::default(),
),
}
}
fn construct_service_endpoint(
did_url: &DIDURL,
did_parameters: &DIDParameters,
service_endpoint_url: &str,
) -> Result<String, String> {
let mut parts = service_endpoint_url.splitn(2, '#');
let service_endpoint_url = parts.next().unwrap();
let input_service_endpoint_fragment = parts.next();
if did_url.fragment.is_some() && input_service_endpoint_fragment.is_some() {
return Err(
"DID URL and input service endpoint URL MUST NOT both have a fragment component"
.to_string(),
);
}
parts = service_endpoint_url.splitn(2, '?');
let service_endpoint_url = parts.next().unwrap();
let input_service_endpoint_query = parts.next();
let did_url_path: String;
let did_url_query: Option<String>;
if let Some(ref relative_ref) = did_parameters.relative_ref {
parts = relative_ref.splitn(2, '?');
did_url_path = parts.next().unwrap().to_owned();
if !did_url.path_abempty.is_empty() {
return Err("DID URL and relativeRef MUST NOT both have a path component".to_string());
}
did_url_query = parts.next().map(|q| q.to_owned());
} else {
did_url_path = did_url.path_abempty.to_owned();
did_url_query = did_url.query.to_owned();
}
if did_url_query.is_some() && input_service_endpoint_query.is_some() {
return Err(
"DID URL and input service endpoint URL MUST NOT both have a query component"
.to_string(),
);
}
let mut output_url = service_endpoint_url.to_owned();
output_url += &did_url_path;
if let Some(query) = input_service_endpoint_query {
output_url.push('?');
output_url.push_str(query);
}
if let Some(query) = did_url_query {
output_url.push('?');
output_url.push_str(&query);
}
if let Some(fragment) = input_service_endpoint_fragment {
output_url.push('#');
output_url.push_str(fragment);
}
if let Some(fragment) = &did_url.fragment {
output_url.push('#');
output_url.push_str(fragment);
}
Ok(output_url)
}
#[cfg(feature = "http")]
#[derive(Debug, Clone, Default)]
pub struct HTTPDIDResolver {
pub endpoint: String,
}
#[cfg(feature = "http")]
impl HTTPDIDResolver {
pub fn new(url: &str) -> Self {
Self {
endpoint: url.to_string(),
}
}
}
#[cfg(feature = "http")]
fn transform_resolution_result(
result: Result<ResolutionResult, serde_json::Error>,
) -> (
ResolutionMetadata,
Option<Document>,
Option<DocumentMetadata>,
) {
let result: ResolutionResult = match result {
Ok(result) => result,
Err(err) => {
return (
ResolutionMetadata::from_error(&format!(
"Error parsing resolution result: {}",
err
)),
None,
None,
)
}
};
let res_meta = if let Some(mut meta) = result.did_resolution_metadata {
meta.content_type = None;
meta
} else {
ResolutionMetadata::default()
};
(res_meta, result.did_document, result.did_document_metadata)
}
#[cfg(feature = "http")]
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl DIDResolver for HTTPDIDResolver {
async fn resolve(
&self,
did: &str,
input_metadata: &ResolutionInputMetadata,
) -> (
ResolutionMetadata,
Option<Document>,
Option<DocumentMetadata>,
) {
let querystring = match serde_urlencoded::to_string(input_metadata) {
Ok(qs) => qs,
Err(err) => {
return (
ResolutionMetadata {
error: Some(
"Unable to serialize input metadata into query string: ".to_string()
+ &err.to_string(),
),
content_type: None,
property_set: None,
},
None,
None,
)
}
};
let did_urlencoded =
percent_encoding::utf8_percent_encode(did, percent_encoding::CONTROLS).to_string();
let mut url = self.endpoint.clone() + &did_urlencoded;
if !querystring.is_empty() {
url.push('?');
url.push_str(&querystring);
}
let url: Url = match url.parse() {
Ok(url) => url,
Err(_) => {
return (
ResolutionMetadata {
error: Some(ERROR_INVALID_DID.to_string()),
content_type: None,
property_set: None,
},
None,
None,
);
}
};
let client = match Client::builder().build() {
Ok(client) => client,
Err(err) => {
return (
ResolutionMetadata::from_error(&format!("Error building HTTP client: {}", err)),
None,
None,
);
}
};
let resp = match client
.get(url)
.header("Accept", TYPE_DID_RESOLUTION)
.header("User-Agent", crate::USER_AGENT)
.send()
.await
{
Ok(resp) => resp,
Err(err) => {
return (
ResolutionMetadata::from_error(&format!("Error sending HTTP request: {}", err)),
None,
None,
)
}
};
let status = resp.status();
let content_type = match resp.headers().get(header::CONTENT_TYPE) {
None => None,
Some(content_type) => Some(String::from(match content_type.to_str() {
Ok(content_type) => content_type,
Err(err) => {
return (
ResolutionMetadata::from_error(&format!(
"Error reading HTTP header: {}",
err
)),
None,
None,
)
}
})),
}
.unwrap_or_default();
let res_result_representation = match resp.bytes().await {
Ok(bytes) => bytes.to_vec(),
Err(err) => {
return (
ResolutionMetadata {
error: Some("Error reading HTTP response: ".to_string() + &err.to_string()),
content_type: None,
property_set: None,
},
None,
None,
)
}
};
if content_type == TYPE_DID_RESOLUTION {
return transform_resolution_result(serde_json::from_slice(&res_result_representation));
}
if status == StatusCode::NOT_FOUND {
return (ResolutionMetadata::from_error(ERROR_NOT_FOUND), None, None);
}
let value: Value = match serde_json::from_slice(&res_result_representation) {
Ok(result) => result,
Err(err) => {
return (
ResolutionMetadata::from_error(&format!(
"Error parsing resolution response: {}",
err
)),
None,
None,
)
}
};
let first_context_uri = get_first_context_uri(&value);
if first_context_uri == Some(DID_RESOLUTION_V1_CONTEXT) {
return transform_resolution_result(serde_json::from_value(value));
}
let doc: Document = match serde_json::from_value(value) {
Ok(doc) => doc,
Err(err) => {
return (
ResolutionMetadata::from_error(&format!("Error parsing DID document: {}", err)),
None,
None,
)
}
};
return (ResolutionMetadata::default(), Some(doc), None);
}
async fn dereference(
&self,
primary_did_url: &PrimaryDIDURL,
input_metadata: &DereferencingInputMetadata,
) -> Option<(DereferencingMetadata, Content, ContentMetadata)> {
let querystring = match serde_urlencoded::to_string(input_metadata) {
Ok(qs) => qs,
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Unable to serialize input metadata into query string: {}",
err
)),
Content::Null,
ContentMetadata::default(),
))
}
};
let did_url_urlencoded = percent_encoding::utf8_percent_encode(
&primary_did_url.to_string(),
percent_encoding::CONTROLS,
)
.to_string();
let mut url = self.endpoint.clone() + &did_url_urlencoded;
if !querystring.is_empty() {
url.push('?');
url.push_str(&querystring);
}
let url: Url = match url.parse() {
Ok(url) => url,
Err(_) => {
return Some((
DereferencingMetadata::from_error(ERROR_INVALID_DID),
Content::Null,
ContentMetadata::default(),
));
}
};
let client = match Client::builder().build() {
Ok(client) => client,
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Error building HTTP client: {}",
err
)),
Content::Null,
ContentMetadata::default(),
));
}
};
let resp = match client
.get(url)
.header("Accept", TYPE_DID_RESOLUTION)
.header("User-Agent", crate::USER_AGENT)
.send()
.await
{
Ok(resp) => resp,
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Error sending HTTP request: {}",
err
)),
Content::Null,
ContentMetadata::default(),
))
}
};
let mut deref_meta = DereferencingMetadata::default();
let mut content = Content::Null;
let mut content_meta = ContentMetadata::default();
deref_meta.error = match resp.status() {
StatusCode::NOT_FOUND => Some(ERROR_NOT_FOUND.to_string()),
StatusCode::BAD_REQUEST => Some(ERROR_INVALID_DID.to_string()),
StatusCode::NOT_ACCEPTABLE => Some(ERROR_REPRESENTATION_NOT_SUPPORTED.to_string()),
_ => None,
};
let content_type = match resp.headers().get(header::CONTENT_TYPE) {
None => None,
Some(content_type) => Some(String::from(match content_type.to_str() {
Ok(content_type) => content_type,
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Error reading HTTP header: {}",
err
)),
Content::Null,
ContentMetadata::default(),
))
}
})),
}
.unwrap_or_default();
let deref_result_bytes = match resp.bytes().await {
Ok(bytes) => bytes.to_vec(),
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Error reading HTTP response: {}",
err
)),
Content::Null,
ContentMetadata::default(),
))
}
};
match &content_type[..] {
TYPE_DID_LD_JSON | TYPE_DID_JSON => {
let doc: Document = match serde_json::from_slice(&deref_result_bytes) {
Ok(result) => result,
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Error parsing DID document: {}",
err
)),
Content::Null,
ContentMetadata::default(),
))
}
};
content = Content::DIDDocument(doc);
content_meta = ContentMetadata::DIDDocument(DocumentMetadata::default());
deref_meta.content_type = Some(TYPE_DID_LD_JSON.to_string());
}
TYPE_DID_RESOLUTION => {
let result: ResolutionResult = match serde_json::from_slice(&deref_result_bytes) {
Ok(result) => result,
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Error parsing DID resolution result: {}",
err
)),
Content::Null,
ContentMetadata::default(),
))
}
};
if let Some(res_meta) = result.did_resolution_metadata {
deref_meta = res_meta.into();
}
if let Some(doc) = result.did_document {
content = Content::DIDDocument(doc);
}
content_meta =
ContentMetadata::DIDDocument(result.did_document_metadata.unwrap_or_default());
}
TYPE_LD_JSON | TYPE_JSON => {
let object: BTreeMap<String, Value> =
match serde_json::from_slice(&deref_result_bytes) {
Ok(result) => result,
Err(err) => {
return Some((
DereferencingMetadata::from_error(&format!(
"Error parsing JSON: {}",
err
)),
Content::Null,
ContentMetadata::default(),
))
}
};
content = Content::Object(Resource::Object(object));
deref_meta.content_type = Some(content_type);
}
_ => {
deref_meta.content_type = Some(content_type);
content = Content::Data(deref_result_bytes.to_vec());
}
}
Some((deref_meta, content, content_meta))
}
}
#[derive(Clone, Default)]
pub struct SeriesResolver<'a> {
pub resolvers: Vec<&'a (dyn DIDResolver)>,
}
#[cfg_attr(target_arch = "wasm32", async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait)]
impl<'a> DIDResolver for SeriesResolver<'a> {
async fn resolve(
&self,
did: &str,
input_metadata: &ResolutionInputMetadata,
) -> (
ResolutionMetadata,
Option<Document>,
Option<DocumentMetadata>,
) {
for resolver in &self.resolvers {
let (res_meta, doc_opt, doc_meta_opt) = resolver.resolve(did, input_metadata).await;
let method_supported = match res_meta.error {
None => true,
Some(ref err) => err != ERROR_METHOD_NOT_SUPPORTED,
};
if method_supported {
return (res_meta, doc_opt, doc_meta_opt);
}
}
(
ResolutionMetadata::from_error(ERROR_METHOD_NOT_SUPPORTED),
None,
None,
)
}
async fn resolve_representation(
&self,
did: &str,
input_metadata: &ResolutionInputMetadata,
) -> (ResolutionMetadata, Vec<u8>, Option<DocumentMetadata>) {
for resolver in &self.resolvers {
let (res_meta, doc_data, doc_meta_opt) =
resolver.resolve_representation(did, input_metadata).await;
let method_supported = match res_meta.error {
None => true,
Some(ref err) => err != ERROR_METHOD_NOT_SUPPORTED,
};
if method_supported {
return (res_meta, doc_data, doc_meta_opt);
}
}
(
ResolutionMetadata::from_error(ERROR_METHOD_NOT_SUPPORTED),
Vec::new(),
None,
)
}
async fn dereference(
&self,
primary_did_url: &PrimaryDIDURL,
input_metadata: &DereferencingInputMetadata,
) -> Option<(DereferencingMetadata, Content, ContentMetadata)> {
for resolver in &self.resolvers {
if let Some((deref_meta, content, content_meta)) =
resolver.dereference(primary_did_url, input_metadata).await
{
let method_supported = match deref_meta.error {
None => true,
Some(ref err) => err != ERROR_METHOD_NOT_SUPPORTED,
};
if method_supported {
return Some((deref_meta, content, content_meta));
}
}
}
None
}
}
pub async fn easy_resolve(did: &str, resolver: &dyn DIDResolver) -> Result<Document, Error> {
let (res_meta, doc_opt, _meta) = resolver
.resolve(did, &ResolutionInputMetadata::default())
.await;
if let Some(err) = res_meta.error {
return Err(Error::UnableToResolve(err));
}
let doc = doc_opt
.ok_or_else(|| Error::UnableToResolve(format!("Missing document for DID: {}", did)))?;
Ok(doc)
}
pub async fn get_verification_methods(
did: &str,
verification_relationship: VerificationRelationship,
resolver: &dyn DIDResolver,
) -> Result<HashMap<String, VerificationMethodMap>, Error> {
get_verification_methods_for_all(&[did], verification_relationship, resolver).await
}
pub async fn get_verification_methods_for_all(
dids: &[&str],
verification_relationship: VerificationRelationship,
resolver: &dyn DIDResolver,
) -> Result<HashMap<String, VerificationMethodMap>, Error> {
let mut did_docs: HashMap<String, Document> = HashMap::new();
let mut dids_to_resolve = dids
.iter()
.copied()
.map(|x| x.to_owned())
.collect::<Vec<String>>(); while let Some(did) = dids_to_resolve.pop() {
if !did_docs.contains_key(&did) {
let doc = easy_resolve(&did, resolver).await?;
for controller in doc.controller.iter().flatten() {
dids_to_resolve.push(controller.clone());
}
if did_docs.len() > MAX_CONTROLLERS {
return Err(Error::ControllerLimit);
}
did_docs.insert(did, doc);
}
}
let mut vm_ids_for_purpose = HashSet::new();
let mut vmms_by_id: HashMap<String, VerificationMethodMap> = HashMap::new();
for (_did, doc) in did_docs {
let vm_ids = doc
.get_verification_method_ids(verification_relationship.clone())
.map_err(|e| {
Error::UnableToResolve(format!("Unable to get verification method ids: {:?}", e))
})?;
for id in vm_ids {
vm_ids_for_purpose.insert(id);
}
for vm in doc
.verification_method
.into_iter()
.chain(doc.public_key.into_iter())
.chain(doc.authentication.into_iter())
.chain(doc.assertion_method.into_iter())
.chain(doc.key_agreement.into_iter())
.chain(doc.capability_invocation.into_iter())
.chain(doc.capability_delegation.into_iter())
.flatten()
{
if let VerificationMethod::Map(mut vmm) = vm {
let vm_id = vmm.get_id(&doc.id);
crate::merge_context(&mut vmm.context, &doc.context);
vmms_by_id.insert(vm_id, vmm);
}
}
}
let mut vmms = HashMap::new();
for id in vm_ids_for_purpose {
let vmm = if let Some(vmm) = vmms_by_id.remove(&id) {
vmm
} else {
resolve_vm(&id, resolver).await?
};
vmms.insert(id, vmm);
}
Ok(vmms)
}
pub async fn resolve_key(
verification_method: &str,
resolver: &dyn DIDResolver,
) -> Result<JWK, Error> {
let vmm = resolve_vm(verification_method, resolver).await?;
let jwk = vmm.get_jwk()?;
Ok(jwk)
}
pub async fn resolve_vm(
verification_method: &str,
resolver: &dyn DIDResolver,
) -> Result<VerificationMethodMap, Error> {
let (res_meta, object, _meta) = dereference(
resolver,
verification_method,
&DereferencingInputMetadata::default(),
)
.await;
if let Some(error) = res_meta.error {
return Err(Error::DIDURLDereference(error));
}
let vm = match object {
Content::Object(Resource::VerificationMethod(vm)) => vm,
Content::Null => return Err(Error::ResourceNotFound(verification_method.to_string())),
_ => return Err(Error::ExpectedObject),
};
Ok(vm)
}
#[cfg(test)]
mod tests {
#[cfg(feature = "http")]
use hyper::{Body, Response, Server};
use super::*;
struct ExampleResolver {}
const EXAMPLE_123_ID: &str = "did:example:123";
const EXAMPLE_123_JSON: &str = r#"{
"@context": "https://www.w3.org/ns/did/v1",
"id": "did:example:123",
"authentication": [
{
"id": "did:example:123#z6MkecaLyHuYWkayBDLw5ihndj3T1m6zKTGqau3A51G7RBf3",
"type": "Ed25519VerificationKey2018",
"controller": "did:example:123",
"publicKeyBase58": "AKJP3f7BD6W4iWEQ9jwndVTCBq8ua2Utt8EEjJ6Vxsf"
}
],
"capabilityInvocation": [
{
"id": "did:example:123#z6MkhdmzFu659ZJ4XKj31vtEDmjvsi5yDZG5L7Caz63oP39k",
"type": "Ed25519VerificationKey2018",
"controller": "did:example:123",
"publicKeyBase58": "4BWwfeqdp1obQptLLMvPNgBw48p7og1ie6Hf9p5nTpNN"
}
],
"capabilityDelegation": [
{
"id": "did:example:123#z6Mkw94ByR26zMSkNdCUi6FNRsWnc2DFEeDXyBGJ5KTzSWyi",
"type": "Ed25519VerificationKey2018",
"controller": "did:example:123",
"publicKeyBase58": "Hgo9PAmfeoxHG8Mn2XHXamxnnSwPpkyBHAMNF3VyXJCL"
}
],
"assertionMethod": [
{
"id": "did:example:123#z6MkiukuAuQAE8ozxvmahnQGzApvtW7KT5XXKfojjwbdEomY",
"type": "Ed25519VerificationKey2018",
"controller": "did:example:123",
"publicKeyBase58": "5TVraf9itbKXrRvt2DSS95Gw4vqU3CHAdetoufdcKazA"
}
]
}"#;
#[cfg(feature = "http")]
const DID_KEY_ID: &str = "did:key:z6Mkfriq1MqLBoPWecGoDLjguo1sB9brj6wT3qZ5BxkKpuP6";
#[cfg(feature = "http")]
const DID_KEY_JSON: &str = include_str!("../../tests/did-key-uniresolver-resp.json");
#[async_trait]
impl DIDResolver for ExampleResolver {
async fn resolve(
&self,
did: &str,
_input_metadata: &ResolutionInputMetadata,
) -> (
ResolutionMetadata,
Option<Document>,
Option<DocumentMetadata>,
) {
if did == EXAMPLE_123_ID {
let doc = match Document::from_json(EXAMPLE_123_JSON) {
Ok(doc) => doc,
Err(err) => {
return (
ResolutionMetadata {
error: Some("JSON Error: ".to_string() + &err.to_string()),
content_type: None,
property_set: None,
},
None,
None,
);
}
};
(
ResolutionMetadata {
content_type: Some(TYPE_DID_LD_JSON.to_string()),
..Default::default()
},
Some(doc),
Some(DocumentMetadata::default()),
)
} else {
(
ResolutionMetadata {
error: Some(ERROR_NOT_FOUND.to_string()),
content_type: None,
property_set: None,
},
None,
None,
)
}
}
async fn resolve_representation(
&self,
did: &str,
_input_metadata: &ResolutionInputMetadata,
) -> (ResolutionMetadata, Vec<u8>, Option<DocumentMetadata>) {
if did == EXAMPLE_123_ID {
let vec = EXAMPLE_123_JSON.as_bytes().to_vec();
(
ResolutionMetadata {
error: None,
content_type: Some(TYPE_DID_LD_JSON.to_string()),
property_set: None,
},
vec,
Some(DocumentMetadata::default()),
)
} else {
(
ResolutionMetadata {
error: Some(ERROR_NOT_FOUND.to_string()),
content_type: None,
property_set: None,
},
Vec::new(),
None,
)
}
}
}
#[async_std::test]
async fn resolve() {
let resolver = ExampleResolver {};
let (res_meta, doc, doc_meta) = resolver
.resolve(EXAMPLE_123_ID, &ResolutionInputMetadata::default())
.await;
assert_eq!(res_meta.error, None);
assert!(doc_meta.is_some());
let doc = doc.unwrap();
assert_eq!(doc.id, EXAMPLE_123_ID);
}
#[async_std::test]
async fn resolve_representation() {
let resolver = ExampleResolver {};
let (res_meta, doc_representation, doc_meta) = resolver
.resolve_representation(EXAMPLE_123_ID, &ResolutionInputMetadata::default())
.await;
assert_eq!(res_meta.error, None);
assert!(doc_meta.is_some());
assert_eq!(doc_representation, EXAMPLE_123_JSON.as_bytes());
}
#[cfg(feature = "http")]
fn did_resolver_server() -> Result<(String, impl FnOnce() -> Result<(), ()>), hyper::Error> {
use hyper::service::{make_service_fn, service_fn};
let addr = ([127, 0, 0, 1], 0).into();
let make_svc = make_service_fn(|_| async {
Ok::<_, hyper::Error>(service_fn(|req| async move {
let uri = req.uri();
let id: String = uri.path().chars().skip(1).collect();
let res_input_meta: ResolutionInputMetadata =
serde_urlencoded::from_str(uri.query().unwrap_or("")).unwrap();
if id == DID_KEY_ID {
let body = Body::from(DID_KEY_JSON);
let mut response = Response::new(body);
response
.headers_mut()
.insert(header::CONTENT_TYPE, TYPE_DID_RESOLUTION.parse().unwrap());
return Ok::<_, hyper::Error>(response);
}
let resolver = ExampleResolver {};
let (res_meta, doc_opt, doc_meta_opt) =
resolver.resolve(&id, &res_input_meta).await;
let (mut parts, _) = Response::<Body>::default().into_parts();
if res_meta.error == Some(ERROR_NOT_FOUND.to_string()) {
parts.status = StatusCode::NOT_FOUND;
}
parts
.headers
.insert(header::CONTENT_TYPE, TYPE_DID_RESOLUTION.parse().unwrap());
let result = ResolutionResult {
did_document: doc_opt,
did_resolution_metadata: Some(res_meta),
did_document_metadata: doc_meta_opt,
..Default::default()
};
let body = Body::from(serde_json::to_vec_pretty(&result).unwrap());
Ok::<_, hyper::Error>(Response::from_parts(parts, body))
}))
});
let server = Server::try_bind(&addr)?.serve(make_svc);
let url = "http://".to_string() + &server.local_addr().to_string() + "/";
let (shutdown_tx, shutdown_rx) = futures::channel::oneshot::channel();
let graceful = server.with_graceful_shutdown(async {
shutdown_rx.await.ok();
});
tokio::task::spawn(async move {
graceful.await.ok();
});
let shutdown = || shutdown_tx.send(());
Ok((url, shutdown))
}
#[tokio::test]
#[cfg(feature = "http")]
async fn http_resolve_representation() {
use serde_json::Value;
let (endpoint, shutdown) = did_resolver_server().unwrap();
let resolver = HTTPDIDResolver { endpoint };
let (res_meta, doc_representation, doc_meta) = resolver
.resolve_representation(EXAMPLE_123_ID, &ResolutionInputMetadata::default())
.await;
assert_eq!(res_meta.error, None);
assert!(doc_meta.is_some());
let doc: Value = serde_json::from_slice(&doc_representation).unwrap();
let doc_expected: Value = serde_json::from_str(EXAMPLE_123_JSON).unwrap();
assert_eq!(doc, doc_expected);
shutdown().ok();
}
#[tokio::test]
#[cfg(feature = "http")]
async fn http_resolve() {
let (endpoint, shutdown) = did_resolver_server().unwrap();
let resolver = HTTPDIDResolver { endpoint };
let (res_meta, doc, doc_meta) = resolver
.resolve(EXAMPLE_123_ID, &ResolutionInputMetadata::default())
.await;
assert_eq!(res_meta.error, None);
assert!(doc_meta.is_some());
let doc = doc.unwrap();
assert_eq!(doc.id, EXAMPLE_123_ID);
shutdown().ok();
}
#[tokio::test]
#[cfg(feature = "http")]
async fn resolve_uniresolver_fixture() {
let id = DID_KEY_ID;
let (endpoint, shutdown) = did_resolver_server().unwrap();
let resolver = HTTPDIDResolver { endpoint };
let (res_meta, doc, doc_meta) = resolver
.resolve(id, &ResolutionInputMetadata::default())
.await;
eprintln!("res_meta = {:?}", &res_meta);
eprintln!("doc_meta = {:?}", &doc_meta);
eprintln!("doc = {:?}", &doc);
assert_eq!(res_meta.error, None);
let doc = doc.unwrap();
assert_eq!(doc.id, id);
shutdown().ok();
}
#[test]
fn service_endpoint_construction() {
use std::str::FromStr;
let input_service_endpoint_url = "https://example.com/messages/8377464";
let input_did_url = DIDURL::from_str("did:example:123456789abcdefghi?service=messages&relative-ref=%2Fsome%2Fpath%3Fquery#frag").unwrap();
let expected_output_service_endpoint_url =
"https://example.com/messages/8377464/some/path?query#frag";
let input_did_parameters: DIDParameters =
serde_urlencoded::from_str(input_did_url.query.as_ref().unwrap()).unwrap();
let output_service_endpoint_url = construct_service_endpoint(
&input_did_url,
&input_did_parameters,
input_service_endpoint_url,
)
.unwrap();
assert_eq!(
output_service_endpoint_url,
expected_output_service_endpoint_url
);
}
#[async_std::test]
async fn dereference_did_url() {
const DID: &str = "did:example:123456789abcdefghi";
const DOC_STR: &str = r###"
{
"@context": "https://www.w3.org/ns/did/v1",
"id": "did:example:123456789abcdefghi",
"verificationMethod": [{
"id": "did:example:123456789abcdefghi#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:example:123456789abcdefghi",
"publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
}, {
"id": "#keys-2",
"type": "Ed25519VerificationKey2018",
"controller": "did:example:123456789abcdefghi",
"publicKeyBase58": "4BWwfeqdp1obQptLLMvPNgBw48p7og1ie6Hf9p5nTpNN"
}],
"service": [{
"id": "did:example:123456789abcdefghi#agent",
"type": "AgentService",
"serviceEndpoint": "https://agent.example.com/8377464"
}, {
"id": "did:example:123456789abcdefghi#messages",
"type": "MessagingService",
"serviceEndpoint": "https://example.com/messages/8377464"
}]
}
"###;
struct DerefExampleResolver;
#[async_trait]
impl DIDResolver for DerefExampleResolver {
async fn resolve(
&self,
did: &str,
_input_metadata: &ResolutionInputMetadata,
) -> (
ResolutionMetadata,
Option<Document>,
Option<DocumentMetadata>,
) {
if did != DID {
panic!("Unexpected DID: {}", did);
}
let doc = Document::from_json(DOC_STR).unwrap();
(
ResolutionMetadata {
content_type: Some(TYPE_DID_LD_JSON.to_string()),
..Default::default()
},
Some(doc),
Some(DocumentMetadata::default()),
)
}
}
let did_url = "did:example:123456789abcdefghi#keys-1";
let expected_output_resource = r#"
{
"@context": "https://www.w3.org/ns/did/v1",
"id": "did:example:123456789abcdefghi#keys-1",
"type": "Ed25519VerificationKey2018",
"controller": "did:example:123456789abcdefghi",
"publicKeyBase58": "H3C2AVvLMv6gmMNam3uVAjZpfkcJCwDwnZn6z3wXmqPV"
}
"#;
let vm: VerificationMethodMap = serde_json::from_str(expected_output_resource).unwrap();
let expected_content = Content::Object(Resource::VerificationMethod(vm));
let (deref_meta, content, content_meta) = dereference(
&DerefExampleResolver,
did_url,
&DereferencingInputMetadata::default(),
)
.await;
assert_eq!(deref_meta.error, None);
assert_eq!(content, expected_content);
eprintln!("dereferencing metadata: {:?}", deref_meta);
eprintln!("content: {:?}", content);
eprintln!("content metadata: {:?}", content_meta);
let did_url = "did:example:123456789abcdefghi?service=messages&relative-ref=%2Fsome%2Fpath%3Fquery#frag";
let expected_output_service_endpoint_url =
"https://example.com/messages/8377464/some/path?query#frag";
let expected_content = Content::URL(expected_output_service_endpoint_url.to_string());
let (deref_meta, content, _content_meta) = dereference(
&DerefExampleResolver,
did_url,
&DereferencingInputMetadata::default(),
)
.await;
assert_eq!(deref_meta.error, None);
assert_eq!(content, expected_content);
let (deref_meta, _content, _content_meta) = dereference(
&DerefExampleResolver,
"did:example:123456789abcdefghi#keys-2",
&DereferencingInputMetadata::default(),
)
.await;
assert_eq!(deref_meta.error, None);
let (deref_meta, _content, _content_meta) = dereference(
&DerefExampleResolver,
"did:example:123456789abcdefghi#nope",
&DereferencingInputMetadata::default(),
)
.await;
assert_ne!(deref_meta.error, None);
}
}