Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(FTL-17164): introduce config and limit did size and no. of parts #46

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ jobs:
with:
auditIgnore: 'RUSTSEC-2022-0040,RUSTSEC-2023-0071'
coverage: 50
useRedis: false
2 changes: 2 additions & 0 deletions Cargo.lock

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

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}"
167 changes: 167 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,167 @@
use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
use std::{
env, fmt,
fs::File,
io::{self, BufRead},
path::{Path, PathBuf},
};

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> {
let cur_working_dir = env::current_dir().unwrap();

let config_relative_path =
"affinidi-did-resolver/affinidi-did-resolver-methods/did-peer/conf/did-peer-conf.toml";
let config_path = _get_relative_path(
cur_working_dir.as_os_str().to_str().unwrap(),
&config_relative_path,

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

View workflow job for this annotation

GitHub Actions / rust-pipeline / Clippy

this expression creates a reference which is immediately dereferenced by the compiler
);
// Read configuration file parameters
let config_raw = read_config_file(&config_path)?;

match Config::try_from(config_raw) {

Check warning on line 129 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),
}
}

fn _get_relative_path(str1: &str, str2: &str) -> String {
let repo_name = "affinidi-did-resolver";
// Convert strings into Path objects
let path1 = Path::new(str1);
let path2 = Path::new(str2);

// Iterate through components and find the common part
let mut up: usize = 0;
let mut common_part = PathBuf::new();
let mut path2_iter = path2.components().peekable();
for component1 in path1.components() {
if let Some(&component2) = path2_iter.peek() {
if component1 == component2 {
common_part.push(component1);
path2_iter.next();
} else if common_part.capacity() > 0
&& component1.as_os_str().to_str().unwrap() != repo_name
{
println!("Component: {:?}", component1);
up += 1;
}
}
}

// Remove the common part from the second path
let mut final_path = PathBuf::new();
let remaining_part: PathBuf = path2_iter.collect();
for _ in 0..up {
final_path.push("..");
}
final_path.push(remaining_part);
final_path.to_str().unwrap_or("").to_string()
}
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
Loading