lib_wraphandlers/reqtasks/src/lib.rs

391 lines
12 KiB
Rust

//
/*! Zterton
*/
// Copyright 2021, Jesús Pérez Lorenzo
//
#![allow(clippy::needless_lifetimes)]
use std::collections::HashMap;
use std::fmt;
use std::str::from_utf8;
use casbin::prelude::*;
use tera::Tera;
use app_env::{
AppStore,
appenv::AppEnv,
config::{Config,WebServer},
module::Module,
appdata::AppData
};
use app_tools::{hash_from_data,read_path_file};
use app_auth::{
AuthStore,
UserCtx,
BEARER_PREFIX,
AuthError,
LoginRequest,
};
use warp::{
http::{header::AUTHORIZATION,method::Method, HeaderMap, HeaderValue},
// Filter,
};
// use salvo::{Request};
use app_errors::AppError;
use std::result::Result;
// use std::io::Result;
/// `ReqTasks` is a facilitator container for http request parsing and response composition
/// Manage info from `AppData` from env, vault, tera, ctx, etc.
/// Evaluate token, session state
/// Control some authorizations and permissions
#[derive(Clone)]
pub struct ReqTasks {
/// AppData object
pub app_data: AppData,
// pub app_data: &'a AppData,
/// Auth object
pub auth_store: AuthStore,
// pub auth_store: &'a AuthStore,
/// Request objet
pub header: HeaderMap<HeaderValue>,
pub method: Method,
pub path: String,
/// It will allow to take decisions based in origin or source context.
pub origin: String,
pub key_module: String,
}
impl fmt::Display for ReqTasks {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} {} {}", &self.path, &self.origin, &self.key_module)
}
}
impl ReqTasks {
#[must_use]
pub fn new(
app_db: AppStore,
auth_store: AuthStore,
header: HeaderMap<HeaderValue>,
method: Method,
path: &str,
origin: &str,
key_module: &str
) -> ReqTasks {
let app_data = app_db.app_data.read();
// let auth_store: &'a AuthStore = &AuthStore {
// users: auth_db.users.clone(),
// sessions: auth_db.sessions.clone(),
// enforcer: auth_db.enforcer.clone(),
// };
ReqTasks {
app_data: app_data.to_owned(),
auth_store: auth_store.to_owned(),
header,
method,
path: path.to_string(),
origin: origin.to_string(),
key_module: key_module.to_string(),
}
}
/// Get `AppEnv`
#[must_use]
pub fn env(&self) -> AppEnv {
self.app_data.env.to_owned()
// self.app_data.env.lock().await.to_owned()
}
/// Get Tera
#[must_use]
pub fn tera(&self) -> tera::Tera {
self.app_data.tera.to_owned()
}
// /// Get Vault
// pub fn vault(&self) -> ServerVault {
// self.app_data.vault.to_owned()
// }
/// Get Context (ctx)
#[must_use]
pub fn ctx(&self) -> tera::Context {
self.app_data.ctx.to_owned()
}
/// Get `AppEnv` Config
#[must_use]
pub fn config(&self) -> Config {
self.app_data.env.config.to_owned()
}
/// Get `AppEnv` Config
#[must_use]
pub fn websrvr(&self) -> WebServer {
self.app_data.env.config.websrvrs[self.app_data.env.curr_web].to_owned()
}
#[must_use]
pub fn module(&self) -> Module {
self.app_data.env.get_module(&self.key_module)
}
/// Get `AppEnv` Config
// #[must_use]
// pub fn params(&self) -> HashMap<String,String> {
// self.req
// .uri()
// .query()
// .map_or_else(HashMap::new, |v| {
// url::form_urlencoded::parse(v.as_bytes())
// .into_owned()
// .collect()
// })
// }
/// Get Lang list from header - accept-language
/// get first one but only root part as first two characters (es in case of es-ES)
/// if lang is not in `WebServer.langs`it will fallback to `Config.default_lang`
#[must_use]
pub fn lang(&self) -> String {
#[allow(unused_assignments)]
let mut lang = self.config().default_lang;
let default_lang = self.config().default_lang;
let langs = self.websrvr().langs;
// As fallback set default_lang
lang = default_lang;
// Get langs list from header
if let Some(languages) = self.header.get("accept-language") {
match languages.to_str() {
Ok(list_lngs) => {
// Get only first one
if let Some(full_lng) = list_lngs.split(',').collect::<Vec<_>>().first() {
// Get root_lng part
let mut end: usize = 0;
full_lng
.chars()
.into_iter()
.take(2)
.for_each(|x| end += x.len_utf8());
let root_lng = &full_lng[..end];
// Filter to check if is in `Config.langs`
let list: Vec<String> = langs
.iter()
.filter(|lng| lng.as_str() == root_lng)
.cloned()
.collect();
if let Some(lng) = list.get(0) {
// Set if it is found and available
lang = lng.to_owned();
}
}
}
Err(e) => println!("Error: {}", e),
}
}
// println!("Lang: {}",&lang);
lang
}
/// Get `Schema` Config
pub async fn is_admin(&self, ky: String) -> bool {
self.env().appkey == ky
}
/// Render a template with context and load `data_path` from `root_path` in case is not empty
/// it use a `data_hash` for context with data if it is not empty
/// if errors it will render a `fallback_template`
#[allow(clippy::too_many_arguments,clippy::missing_errors_doc)]
pub async fn render_template(
&self,
ctx: &mut tera::Context,
root_path: &str,
template_name: &str,
fallback_template: &str,
data_path: &str,
data_hash: &mut HashMap<String, String>,
app_module: &str,
) -> Result<String,AppError> {
let tera: &mut Tera = &mut self.tera(); //.to_owned();
if ! app_module.is_empty() {
AppData::load_modules(&self.env(), &app_module,ctx);
}
Ok(if data_path.is_empty() {
tera
.render(&template_name, &ctx)
.map_err(|e| AppError::ErrorInternalServerError(format!("Error: {}", e)))?
} else {
let render_fallback = |e| -> Result<String,AppError> {
println!("Error parsing data {}:\n{}", &data_path, e);
Ok(tera
.render(fallback_template, &tera::Context::new())
.map_err(|e| AppError::ErrorInternalServerError(format!("Template error {}",e)))?)
};
match hash_from_data(format!("{}/{}", &root_path, &data_path).as_str(), ctx, data_hash, true) {
Ok(_) => {
let mut need_reload = false;
for (key, value) in data_hash.iter() {
if key.as_str() == "need_reload" {
need_reload = true;
} else {
ctx.insert(key, value);
}
}
if need_reload {
match read_path_file(&root_path, &template_name, "content") {
Ok(tpl) => {
println!("reload {}",&template_name);
tera.add_raw_templates(vec![(&template_name, tpl)])
.map_err(|e| AppError::ErrorInternalServerError(format!("Error reload: {}", e)))?
},
Err(e) => return render_fallback(e),
};
}
tera
.render(&template_name, &ctx)
.map_err(|e| AppError::ErrorInternalServerError(format!("Error render: {}", e)))?
}
Err(e) => {
println!("Error parsing data {}:\n{}", &data_path, e);
return render_fallback(e);
}
}
})
}
#[allow(clippy::too_many_arguments,clippy::missing_errors_doc)]
pub async fn render_page(
&self,
ctx: &mut tera::Context,
root_path: &str,
template_name: &str,
fallback_template: &str,
data_path: &str,
data_hash: &mut HashMap<String, String>,
app_module: &str,
) -> Result<String,AppError> {
let tera = &self.tera();
let res = match self
.render_template(
ctx,
root_path,
template_name,
fallback_template,
data_path,
data_hash,
app_module,
)
.await
{
Ok(res) => res,
Err(e) => {
println!(
"Error render template + data {} + {}:\n{}",
&template_name, &data_path, e
);
tera
.render(&fallback_template, &tera::Context::new())
.map_err(|e| AppError::ErrorInternalServerError(format!("Template error {}",e)))?
}
};
// Ok(HttpResponse::Ok().content_type("text/html").body(res))
Ok(res)
}
#[allow(clippy::missing_errors_doc)]
pub async fn render_to(
&self,
template_name: &str,
data_path: &str,
data_hash: &mut HashMap<String, String>,
app_module: &str,
) -> Result<String,AppError> {
let app_env = &self.env();
// From Default Context
let mut ctx = self.ctx().to_owned();
let fallback_template = "index.html";
self
.render_page(
&mut ctx,
app_env.get_curr_websrvr_config().templates_path.as_str(),
template_name,
fallback_template,
data_path,
data_hash,
app_module,
)
.await
}
#[allow(clippy::missing_errors_doc)]
pub fn token_from_header(&self) -> anyhow::Result<String> {
if let Some(header) = self.header.get(AUTHORIZATION) {
let auth_header = match from_utf8(header.as_bytes()) {
Ok(v) => v,
Err(_) => return Err(AuthError::NoAuthHeaderFoundError.into()),
};
if !auth_header.starts_with(BEARER_PREFIX) {
return Err(AuthError::InvalidAuthHeaderFormatError.into());
}
let without_prefix = auth_header.trim_start_matches(BEARER_PREFIX);
Ok(without_prefix.to_owned())
} else {
Err(AuthError::NoAuthHeaderFoundError.into())
}
}
#[allow(clippy::missing_errors_doc)]
pub async fn token_session(&self, login: &LoginRequest) -> anyhow::Result<String> {
// dbg!("{}",login);
if let Some(user) = &self.auth_store.users
.read()
.await
.iter()
.find(|(_, v)| v.name == *login.name)
// .filter(|(_, v)| *v.name == name)
// .nth(0)
{
let shdw = &self.auth_store.shadows.read().await;
if let Some(usr_shdw) = shdw.get(&String::from(user.0)) {
// println!("{}",&usr_shdw.passwd);
// println!("{}",&login.passwd);
// println!("{}",&user.0);
if usr_shdw.passwd == login.passwd {
// println!("OK ");
let token = uuid::Uuid::new_v4().to_string();
self.auth_store.sessions.write().await.insert(token.clone(), String::from(user.0));
return Ok(token);
}
}
Err(AuthError::UserNotFoundError.into())
//shdw.get(usr_key);
} else {
Err(AuthError::UserNotFoundError.into())
}
}
#[allow(clippy::missing_errors_doc)]
pub async fn user_authentication(&self) -> anyhow::Result<UserCtx> {
let token = self.token_from_header().unwrap_or_else(|e| { println!("{}",e); String::from("")});
self.check_authentication(token).await
}
#[allow(clippy::missing_errors_doc)]
pub async fn check_authentication(&self, token: String) -> anyhow::Result<UserCtx> {
if let Some(user_id) = self.auth_store.sessions.read().await.get(&token) {
if let Some(user) = self.auth_store.users.read().await.get(user_id) {
// println!("{} - {} - {}",&user.role.as_str(), &self.path.as_str(), &self.method.as_str());
match self.auth_store.enforcer.enforce(
(&user.role.as_str(), &self.path.as_str(), &self.method.as_str())
)
{
Ok(authorized) => {
if authorized {
Ok(UserCtx { user_id: user.user_id.to_owned(), token })
} else {
Err(AuthError::UnauthorizedError.into())
}
}
Err(e) => {
eprintln!("error during authorization: {}", e);
Err(AuthError::AuthorizationError.into())
}
}
} else {
Err(AuthError::InvalidTokenError.into())
}
} else {
Err(AuthError::InvalidTokenError.into())
}
}
}