use crate::domain::dtos::{
guest_role::Permission, native_error_codes::NativeErrorCodes,
};
use base64::{engine::general_purpose, Engine};
use mycelium_base::utils::errors::{execution_err, MappedErrors};
use reqwest::Url;
use serde::{Deserialize, Serialize};
use std::str::FromStr;
use utoipa::{ToResponse, ToSchema};
use uuid::Uuid;
#[derive(
Clone, Debug, Deserialize, Serialize, ToSchema, PartialEq, ToResponse,
)]
#[serde(rename_all = "camelCase")]
pub struct LicensedResource {
#[serde(alias = "guest_account_id")]
pub acc_id: Uuid,
#[serde(alias = "guest_account_is_default")]
pub sys_acc: bool,
pub tenant_id: Uuid,
#[serde(alias = "guest_account_name")]
pub acc_name: String,
pub role: String,
pub role_id: Uuid,
#[serde(alias = "permission")]
pub perm: Permission,
pub verified: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub permit_flags: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub deny_flags: Option<Vec<String>>,
}
impl LicensedResource {
pub(super) fn is_uuid(value: &str) -> bool {
let uuid_format = vec![8, 4, 4, 4, 12];
let mut chars = value.chars().peekable();
for &count in &uuid_format {
for _ in 0..count {
match chars.next() {
Some(c) if c.is_ascii_hexdigit() => continue,
_ => return false,
}
}
if let Some('-') = chars.peek() {
chars.next();
}
}
chars.next().is_none()
}
pub fn load_uuid(value: String) -> Result<Uuid, MappedErrors> {
match Uuid::from_str(&value) {
Ok(uuid) => Ok(uuid),
Err(_) => execution_err(format!("Invalid UUID: {}", value))
.with_code(NativeErrorCodes::MYC00019)
.with_exp_true()
.as_error(),
}
}
}
impl ToString for LicensedResource {
fn to_string(&self) -> String {
let encoded_account_name =
general_purpose::STANDARD.encode(self.acc_name.as_bytes());
let mut result = format!(
"t/{tenant_id}/a/{acc_id}/r/{role_id}?p={role}:{perm}&s={is_acc_std}&v={verified}&n={acc_name}",
tenant_id = self.tenant_id.to_string().replace("-", ""),
acc_id = self.acc_id.to_string().replace("-", ""),
role_id = self.role_id.to_string().replace("-", ""),
role = self.role,
perm = self.perm.to_owned().to_i32(),
is_acc_std = self.sys_acc as i8,
verified = self.verified as i8,
acc_name = encoded_account_name,
);
if let Some(permit_flags) = &self.permit_flags {
result += &format!("&pf={}", permit_flags.join(","));
}
if let Some(deny_flags) = &self.deny_flags {
result += &format!("&df={}", deny_flags.join(","));
}
result
}
}
impl FromStr for LicensedResource {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let full_url = format!("https://localhost.local/{s}");
let url = Url::from_str(&full_url).map_err(|e| {
format!("Unexpected error on check license URL: {:?}", e)
})?;
let segments: Vec<_> =
url.path_segments().ok_or("Path not found")?.collect();
if segments.len() != 6
|| segments[0] != "t"
|| segments[2] != "a"
|| segments[4] != "r"
{
return Err("Invalid path format".to_string());
}
let tenant_id = segments[1];
let account_id = segments[3];
let role_id = segments[5];
if !Self::is_uuid(tenant_id) {
return Err("Invalid tenant UUID".to_string());
}
if !Self::is_uuid(account_id) {
return Err("Invalid account UUID".to_string());
}
if !Self::is_uuid(role_id) {
return Err("Invalid role UUID".to_string());
}
let permissioned_role = url
.query_pairs()
.find(|(key, _)| key == "p")
.map(|(_, value)| value)
.ok_or("Parameter permissions not found")?;
let permissioned_role: Vec<_> = permissioned_role.split(':').collect();
if permissioned_role.len() != 2 {
return Err("Invalid permissioned role format".to_string());
}
let role_name = permissioned_role[0];
let permission_code = permissioned_role[1];
let sys = match url
.query_pairs()
.find(|(key, _)| key == "s")
.map(|(_, value)| value)
.ok_or("Parameter sys not found")?
.parse::<i8>()
{
Ok(sys) => match sys {
0 => false,
1 => true,
_ => {
return Err("Invalid account standard".to_string());
}
},
Err(_) => {
return Err("Failed to parse account standard".to_string());
}
};
let verified = match url
.query_pairs()
.find(|(key, _)| key == "v")
.map(|(_, value)| value)
.ok_or("Parameter v not found")?
.parse::<i8>()
{
Ok(verified) => match verified {
0 => false,
1 => true,
_ => {
return Err("Invalid account verification".to_string());
}
},
Err(_) => {
return Err("Failed to parse account verification".to_string());
}
};
let name_encoded = url
.query_pairs()
.find(|(key, _)| key == "n")
.map(|(_, value)| value)
.ok_or("Parameter name not found")?;
let name_decoded =
match general_purpose::STANDARD.decode(name_encoded.as_bytes()) {
Ok(name) => name,
Err(_) => {
return Err("Failed to decode account name".to_string());
}
};
let permit_flags = match url
.query_pairs()
.find(|(key, _)| key == "pf")
.map(|(_, value)| value)
{
Some(value) => {
Some(value.split(',').map(|i| i.to_string()).collect())
}
None => None,
};
let deny_flags = match url
.query_pairs()
.find(|(key, _)| key == "df")
.map(|(_, value)| value)
{
Some(value) => {
Some(value.split(',').map(|i| i.to_string()).collect())
}
None => None,
};
Ok(Self {
tenant_id: Uuid::from_str(tenant_id).unwrap(),
acc_id: Uuid::from_str(account_id).unwrap(),
role_id: Uuid::from_str(role_id).unwrap(),
role: role_name.to_string(),
perm: Permission::from_i32(permission_code.parse::<i32>().unwrap()),
sys_acc: sys,
acc_name: String::from_utf8(name_decoded).unwrap(),
verified,
permit_flags,
deny_flags,
})
}
}
#[derive(
Clone, Debug, Deserialize, Serialize, ToSchema, PartialEq, ToResponse,
)]
#[serde(rename_all = "camelCase")]
pub enum LicensedResources {
Records(Vec<LicensedResource>),
Urls(Vec<String>),
}
impl LicensedResources {
pub fn to_licenses_vector(&self) -> Vec<LicensedResource> {
match self {
Self::Records(records) => records.to_owned(),
Self::Urls(urls) => urls
.iter()
.map(|i| LicensedResource::from_str(i).unwrap())
.collect(),
}
}
}