use std::fs::File;
use std::io::prelude::*;
use std::path::Path;
use std::collections::HashMap;
use serde_json::value::{Map, Value as Json};
use serde::de;
use serde_derive::{Serialize, Deserialize};
use handlebars::Handlebars;
#[derive(Debug, PartialEq, Copy, Clone, Serialize, Deserialize)]
pub enum Permission {
C,
R,
U,
D,
CR,
CU,
CD,
RU,
RD,
UD,
CRU,
RUD,
CUD,
CRD,
CRUD,
None,
}
impl Permission {
pub fn to_binary(&self) -> u32 {
match self {
Permission::C => 0b1000,
Permission::R => 0b0100,
Permission::U => 0b0010,
Permission::D => 0b0001,
Permission::CR => 0b1100,
Permission::CU => 0b1010,
Permission::CD => 0b1001,
Permission::RU => 0b0110,
Permission::RD => 0b0101,
Permission::UD => 0b0011,
Permission::CRU => 0b1110,
Permission::RUD => 0b0111,
Permission::CUD => 0b1011,
Permission::CRD => 0b1101,
Permission::CRUD => 0b1111,
Permission::None => 0b0000,
}
}
pub fn from_binary(perm_num: u32) -> Permission {
match perm_num {
0b1000 => Permission::C,
0b0100 => Permission::R,
0b0010 => Permission::U,
0b0001 => Permission::D,
0b1100 => Permission::CR,
0b1010 => Permission::CU,
0b1001 => Permission::CD,
0b0110 => Permission::RU,
0b0101 => Permission::RD,
0b0011 => Permission::UD,
0b1110 => Permission::CRU,
0b0111 => Permission::RUD,
0b1011 => Permission::CUD,
0b1101 => Permission::CRD,
0b1111 => Permission::CRUD,
_ => Permission::None,
}
}
pub fn has_create_right(&self) -> bool {
match self {
Permission::C => true,
Permission::CR => true,
Permission::CU => true,
Permission::CD => true,
Permission::CRU => true,
Permission::CUD => true,
Permission::CRD => true,
Permission::CRUD => true,
_ => false,
}
}
pub fn has_read_right(&self) -> bool {
match self {
Permission::R => true,
Permission::CR => true,
Permission::RU => true,
Permission::RD => true,
Permission::CRU => true,
Permission::RUD => true,
Permission::CRD => true,
Permission::CRUD => true,
_ => false,
}
}
pub fn has_update_right(&self) -> bool {
match self {
Permission::U => true,
Permission::CU => true,
Permission::RU => true,
Permission::UD => true,
Permission::CRU => true,
Permission::RUD => true,
Permission::CUD => true,
Permission::CRUD => true,
_ => false
}
}
pub fn has_delete_right(&self) -> bool {
match self {
Permission::D => true,
Permission::CD => true,
Permission::RD => true,
Permission::UD => true,
Permission::RUD => true,
Permission::CUD => true,
Permission::CRD => true,
Permission::CRUD => true,
_ => false,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct User2Role {
user_id: String,
roles: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Role2Permission {
role: String,
pub permission: Permission,
pub condition: Option<String>
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Resource2Role2Permission {
pub resource: String,
pub description: String,
pub role2permissions: Vec<Role2Permission>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Resource2Permission {
pub resource: String,
pub permission: Permission,
pub conditions: Vec<String>
}
impl Resource2Permission {
pub fn default() -> Resource2Permission {
Resource2Permission {
resource: String::new(),
permission: Permission::None,
conditions: Vec::new(),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Authorization {
user2roles: Vec<User2Role>,
resource2role2permissions: Vec<Resource2Role2Permission>,
user2permissions: HashMap<String, Vec<Resource2Permission>>,
}
impl Authorization {
pub fn load(res_perm_file: &str, user_role_file: &str) -> Authorization {
let mut s = Self::get_content(res_perm_file);
let perms: Vec<Resource2Role2Permission> = Self::get_json(&s);
s = Self::get_content(user_role_file);
let u2rs: Vec<User2Role> = Self::get_json(&s);
Authorization {
user2roles: u2rs,
resource2role2permissions: perms,
user2permissions: HashMap::new()
}
}
fn get_json<'a, T: de::Deserialize<'a>>(s: &'a str) -> Vec<T> {
let result: Vec<T> = match serde_json::from_str(&s) {
Ok(json) => json,
Err(err) => {
eprintln!("error occurred: Authorization > lib > get_json(str): {}", err);
Vec::new()
}
};
result
}
fn get_content(file: &str) -> String {
let path = Path::new(file);
let display = path.display();
let mut file = match File::open(&path) {
Err(why) => panic!("couldn't open {}: {}", display, why),
Ok(file) => file,
};
let mut content = String::new();
match file.read_to_string(&mut content) {
Err(why) => panic!("couldn't read {}: {}", display, why),
Ok(_) => (),
}
content
}
fn get_roles_for(&self, cid: &str) -> Vec<String> {
let mut result = Vec::new();
for u2r in &self.user2roles {
if &u2r.user_id == cid {
result = u2r.roles.clone();
}
}
result
}
fn get_permissions_for(&self, cid: &str) -> Vec<Resource2Permission> {
self.user2permissions.get(cid).unwrap().to_vec()
}
fn get_resource_permissions_for(&self, cid: &str, res: &str) -> Resource2Permission {
let res_perm = self.get_permissions_for(cid).into_iter().find(|each| each.resource == res);
match res_perm {
Some(r2p) => r2p,
None => Resource2Permission::default()
}
}
pub fn has_permissions_for(&self, cid: &str) -> bool {
self.user2permissions.contains_key(cid)
}
pub fn set_permissions_for(&mut self, cid: &str) {
if !self.user2permissions.contains_key(cid) {
let perms: Vec<Resource2Permission> = self.determine_permissions_for(cid);
self.user2permissions.insert(cid.to_string(), perms.clone());
}
}
fn determine_permissions_for(&self, cid: &str) -> Vec<Resource2Permission> {
let roles: Vec<String> = self.get_roles_for(cid);
let mut result: Vec<Resource2Permission> = Vec::new();
for re2ro2pe in self.resource2role2permissions.clone() {
let ro2perms: Vec<Role2Permission> = re2ro2pe.role2permissions.clone().into_iter().filter(|role2perm|
roles.contains(&role2perm.role)
).collect();
if ro2perms.len() > 0 {
let (permission, conditions) = Self::get_permission_and_conditions(ro2perms);
let re2pe = Resource2Permission {
resource: re2ro2pe.resource,
permission: permission,
conditions: conditions
};
result.push(re2pe);
}
}
result
}
fn get_permission_and_conditions(ro2perms: Vec<Role2Permission>) -> (Permission, Vec<String>) {
let mut result_perm = 0b0000u32;
let mut result_conditions: Vec<String> = Vec::new();
for ro2perm in ro2perms {
result_perm = result_perm | ro2perm.permission.to_binary();
if let Some(cond) = ro2perm.condition {
result_conditions.push(cond)
}
}
(Permission::from_binary(result_perm), result_conditions)
}
pub fn allows_add(&self, cid: &str, res: &str, data: &Map<String, Json>) -> bool {
self.allows("create", cid, res, data)
}
pub fn allows_view(&self, cid: &str, res: &str, data: &Map<String, Json>) -> bool {
self.allows("read", cid, res, data)
}
pub fn allows_edit(&self, cid: &str, res: &str, data: &Map<String, Json>) -> bool {
self.allows("update", cid, res, data)
}
pub fn allows_delete(&self, cid: &str, res: &str, data: &Map<String, Json>) -> bool {
self.allows("delete", cid, res, data)
}
fn allows(&self, op: &str, cid: &str, res: &str, data: &Map<String, Json>) -> bool {
let res_perms = self.get_resource_permissions_for(cid, res);
let has_right = match op {
"create" => res_perms.permission.has_create_right(),
"read" => res_perms.permission.has_read_right(),
"update" => res_perms.permission.has_update_right(),
"delete" => res_perms.permission.has_delete_right(),
_ => res_perms.permission.has_read_right(),
};
if res_perms.conditions.is_empty() {
has_right
} else {
has_right && Self::conditions_satisfied(res_perms.conditions, data)
}
}
fn conditions_satisfied(conditions: Vec<String>, data: &Map<String, Json>) -> bool {
let evaluated_conditions: Vec<bool> = conditions.into_iter()
.map(|each| Handlebars::new().render_template(&each, data).unwrap() )
.collect::<Vec<String>>().into_iter()
.map(|each| Self::evaluate_condition(each) ).collect();
let mut satisfied = true;
for each in evaluated_conditions {
satisfied &= each;
}
satisfied
}
fn evaluate_condition(condition: String) -> bool {
let parts: Vec<&str> = condition.split(" ").collect();
let left = parts[0].trim();
let operator = parts[1].trim();
let right = parts[2].trim();
if left.is_empty() || right.is_empty() {
return false;
}
match operator {
"==" => left == right,
"!=" => left != right,
">" => left > right,
">=" => left >= right,
"<" => left < right,
"<=" => left <= right,
_ => false
}
}
}