Skip to content

Commit

Permalink
feat: introduce config and limit did size and no. of parts
Browse files Browse the repository at this point in the history
  • Loading branch information
YoussefAWasfy committed Oct 11, 2024
1 parent 5bdb891 commit 5509388
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 0 deletions.
2 changes: 2 additions & 0 deletions affinidi-did-resolver-methods/did-peer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ ssi.workspace = true
thiserror.workspace = true
wasm-bindgen.workspace = true
wasm-bindgen-futures.workspace = true
toml.workspace = true
regex.workspace = true

[dev-dependencies]
askar-crypto.workspace = true
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
### max_did_size_in_kb: Maximum size in KB of did to be resolved as FLOAT
### default: 1
max_did_size_in_kb = "${MAX_DID_SIZE_IN_KB:1.0}"

### max_did_parts: Maximum number of parts after splitting did on "."
### Default: 5
max_did_parts = "${MAX_DID_PARTS:5}"
125 changes: 125 additions & 0 deletions affinidi-did-resolver-methods/did-peer/src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
use std::{
env, fmt,
fs::File,
io::{self, BufRead},
path::Path,
};

use crate::DIDPeerError;

/// ConfigRaw Struct is used to deserialize the configuration file
/// We then convert this to the CacheConfig Struct
#[derive(Debug, Serialize, Deserialize)]
struct ConfigRaw {
pub max_did_size_in_kb: String,
pub max_did_parts: String,
}
#[derive(Clone)]
pub struct Config {
pub max_did_size_in_kb: f64,
pub max_did_parts: usize,
}

impl fmt::Debug for Config {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Config")
.field("max_did_size_in_kb", &self.max_did_size_in_kb)
.field("max_did_parts", &self.max_did_parts)
.finish()
}
}

impl Default for Config {
fn default() -> Self {
Config {
max_did_size_in_kb: 1.0,
max_did_parts: 5,
}
}
}

impl TryFrom<ConfigRaw> for Config {
type Error = DIDPeerError;

fn try_from(raw: ConfigRaw) -> Result<Self, Self::Error> {
println!("RAW conf: {:?}", raw.max_did_size_in_kb);
println!("RAW conf: {:?}", raw.max_did_size_in_kb);
Ok(Config {
max_did_parts: raw.max_did_parts.parse().unwrap_or(5),
max_did_size_in_kb: raw.max_did_size_in_kb.parse::<f64>().unwrap_or(1.0),
})
}
}

/// Read the primary configuration file for the mediator
/// Returns a ConfigRaw struct, that still needs to be processed for additional information
/// and conversion to Config struct
fn read_config_file(file_name: &str) -> Result<ConfigRaw, DIDPeerError> {
// Read configuration file parameters
let raw_config = read_file_lines(file_name)?;

let config_with_vars = expand_env_vars(&raw_config);
match toml::from_str(&config_with_vars.join("\n")) {
Ok(config) => Ok(config),
Err(err) => Err(DIDPeerError::ConfigError(format!(
"Could not parse configuration settings. Reason: {:?}",
err
))),
}
}

/// Reads a file and returns a vector of strings, one for each line in the file.
/// It also strips any lines starting with a # (comments)
/// You can join the Vec back into a single string with `.join("\n")`
pub(crate) fn read_file_lines<P>(file_name: P) -> Result<Vec<String>, DIDPeerError>
where
P: AsRef<Path>,
{
let file = File::open(file_name.as_ref()).map_err(|err| {
DIDPeerError::ConfigError(format!(
"Could not open file({}). {}",
file_name.as_ref().display(),
err
))
})?;

let mut lines = Vec::new();
for line in io::BufReader::new(file).lines().map_while(Result::ok) {
// Strip comments out
if !line.starts_with('#') {
lines.push(line);
}
}

Ok(lines)
}

/// Replaces all strings ${VAR_NAME:default_value}
/// with the corresponding environment variables (e.g. value of ${VAR_NAME})
/// or with `default_value` if the variable is not defined.
fn expand_env_vars(raw_config: &Vec<String>) -> Vec<String> {
let re = Regex::new(r"\$\{(?P<env_var>[A-Z_]{1,}[0-9A-Z_]*):(?P<default_value>.*)\}").unwrap();
let mut result: Vec<String> = Vec::new();
for line in raw_config {
result.push(
re.replace_all(line, |caps: &Captures| match env::var(&caps["env_var"]) {
Ok(val) => val,
Err(_) => (caps["default_value"]).into(),
})
.into_owned(),
);
}
result
}

pub fn init() -> Result<Config, DIDPeerError> {
// Read configuration file parameters
let config_raw = read_config_file("conf/did-peer-conf.toml")?;

match Config::try_from(config_raw) {

Check warning on line 121 in affinidi-did-resolver-methods/did-peer/src/config.rs

View workflow job for this annotation

GitHub Actions / rust-pipeline / Clippy

this match expression is unnecessary
Ok(parsed_config) => Ok(parsed_config),
Err(err) => Err(err),
}
}
25 changes: 25 additions & 0 deletions affinidi-did-resolver-methods/did-peer/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@
//! }
//! ```
//!
mod config;

use base64::prelude::*;
use config::init;
use iref::UriBuf;
use serde::{Deserialize, Serialize};
use serde_json::Value;
Expand All @@ -42,6 +45,8 @@ use std::{collections::BTreeMap, fmt};
use thiserror::Error;
use wasm_bindgen::prelude::*;

const BYTES_PER_KILO_BYTE: f64 = 1000.0;

#[derive(Error, Debug)]
pub enum DIDPeerError {
#[error("Unsupported key type")]
Expand All @@ -62,6 +67,8 @@ pub enum DIDPeerError {
JsonParsingError(String),
#[error("Internal error: {0}")]
InternalError(String),
#[error("Configuration Error: {0}")]
ConfigError(String),
}

// Converts DIDPeerError to JsValue which is required for propagating errors to WASM
Expand Down Expand Up @@ -268,6 +275,17 @@ impl DIDMethodResolver for DIDPeer {
method_specific_id: &'a str,
options: Options,
) -> Result<Output<Vec<u8>>, Error> {
let config = init().unwrap();
let did_size_in_kb = method_specific_id.len() as f64 / BYTES_PER_KILO_BYTE;

// If DID's size is greater than 1KB we don't resolve it
if did_size_in_kb > config.max_did_size_in_kb {
return Err(Error::InvalidMethodSpecificId(format!(
"Method specific id's size: {:.3} is greater than 1KB, size must be less than 1KB",
did_size_in_kb
)));
}

// If did:peer is type 0, then treat it as a did:key
if let Some(id) = method_specific_id.strip_prefix('0') {
return DIDKey.resolve_method_representation(id, options).await;
Expand Down Expand Up @@ -298,6 +316,13 @@ impl DIDMethodResolver for DIDPeer {
let mut key_count: u32 = 1;
let mut service_idx: u32 = 0;

if parts.len() > config.max_did_parts {
return Err(Error::InvalidMethodSpecificId(format!(
"Must have less than or equal 5 keys and/or services combined, found {}",
parts.len()
)));
}

for part in parts {
let ch = part.chars().next();
match ch {
Expand Down

0 comments on commit 5509388

Please sign in to comment.