use crate::{
adapter::{Adapter, Filter},
error::ModelError,
model::Model,
Result,
};
#[cfg(feature = "runtime-async-std")]
use async_std::{
fs::File,
io::prelude::*,
io::{BufReader, Error as IoError, ErrorKind},
path::Path,
prelude::*,
};
#[cfg(feature = "runtime-tokio")]
use std::{
io::{Error as IoError, ErrorKind},
path::Path,
};
#[cfg(feature = "runtime-tokio")]
use tokio::{
fs::File,
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
stream::StreamExt,
};
use async_trait::async_trait;
use std::convert::AsRef;
pub struct FileAdapter<P> {
file_path: P,
is_filtered: bool,
}
type LoadPolicyFileHandler = fn(String, &mut dyn Model);
type LoadFilteredPolicyFileHandler<'a> =
fn(String, &mut dyn Model, f: &Filter<'a>) -> bool;
impl<P> FileAdapter<P>
where
P: AsRef<Path> + Send + Sync,
{
pub fn new(p: P) -> FileAdapter<P> {
FileAdapter {
file_path: p,
is_filtered: false,
}
}
async fn load_policy_file(
&self,
m: &mut dyn Model,
handler: LoadPolicyFileHandler,
) -> Result<()> {
let f = File::open(&self.file_path).await?;
let mut lines = BufReader::new(f).lines();
while let Some(line) = lines.next().await {
handler(line?, m);
}
Ok(())
}
async fn load_filtered_policy_file<'a>(
&self,
m: &mut dyn Model,
filter: Filter<'a>,
handler: LoadFilteredPolicyFileHandler<'a>,
) -> Result<bool> {
let f = File::open(&self.file_path).await?;
let mut lines = BufReader::new(f).lines();
let mut is_filtered = false;
while let Some(line) = lines.next().await {
if handler(line?, m, &filter) {
is_filtered = true;
}
}
Ok(is_filtered)
}
async fn save_policy_file(&self, text: String) -> Result<()> {
let mut file = File::create(&self.file_path).await?;
file.write_all(text.as_bytes()).await?;
Ok(())
}
}
#[async_trait]
impl<P> Adapter for FileAdapter<P>
where
P: AsRef<Path> + Send + Sync,
{
async fn load_policy(&self, m: &mut dyn Model) -> Result<()> {
self.load_policy_file(m, load_policy_line).await?;
Ok(())
}
async fn load_filtered_policy<'a>(
&mut self,
m: &mut dyn Model,
f: Filter<'a>,
) -> Result<()> {
self.is_filtered = self
.load_filtered_policy_file(m, f, load_filtered_policy_line)
.await?;
Ok(())
}
async fn save_policy(&mut self, m: &mut dyn Model) -> Result<()> {
if self.file_path.as_ref().as_os_str().is_empty() {
return Err(IoError::new(
ErrorKind::Other,
"save policy failed, file path is empty",
)
.into());
}
let mut policies = String::new();
let ast_map = m.get_model().get("p").ok_or_else(|| {
ModelError::P("Missing policy definition in conf file".to_owned())
})?;
for (ptype, ast) in ast_map {
for rule in ast.get_policy() {
policies.push_str(&format!("{}, {}\n", ptype, rule.join(",")));
}
}
if let Some(ast_map) = m.get_model().get("g") {
for (ptype, ast) in ast_map {
for rule in ast.get_policy() {
policies.push_str(&format!(
"{}, {}\n",
ptype,
rule.join(",")
));
}
}
}
self.save_policy_file(policies).await?;
Ok(())
}
async fn clear_policy(&mut self) -> Result<()> {
self.save_policy_file(String::new()).await?;
Ok(())
}
async fn add_policy(
&mut self,
_sec: &str,
_ptype: &str,
_rule: Vec<String>,
) -> Result<bool> {
Ok(true)
}
async fn add_policies(
&mut self,
_sec: &str,
_ptype: &str,
_rules: Vec<Vec<String>>,
) -> Result<bool> {
Ok(true)
}
async fn remove_policy(
&mut self,
_sec: &str,
_ptype: &str,
_rule: Vec<String>,
) -> Result<bool> {
Ok(true)
}
async fn remove_policies(
&mut self,
_sec: &str,
_ptype: &str,
_rule: Vec<Vec<String>>,
) -> Result<bool> {
Ok(true)
}
async fn remove_filtered_policy(
&mut self,
_sec: &str,
_ptype: &str,
_field_index: usize,
_field_values: Vec<String>,
) -> Result<bool> {
Ok(true)
}
fn is_filtered(&self) -> bool {
self.is_filtered
}
}
fn load_policy_line(line: String, m: &mut dyn Model) {
if line.is_empty() || line.starts_with('#') {
return;
}
if let Some(tokens) = csv::parse(line) {
let key = &tokens[0];
if let Some(ref sec) = key.chars().next().map(|x| x.to_string()) {
if let Some(ast_map) = m.get_mut_model().get_mut(sec) {
if let Some(ast) = ast_map.get_mut(key) {
ast.policy.insert(tokens[1..].to_vec());
}
}
}
}
}
fn load_filtered_policy_line<'a>(
line: String,
m: &mut dyn Model,
f: &Filter<'a>,
) -> bool {
if line.is_empty() || line.starts_with('#') {
return false;
}
if let Some(tokens) = csv::parse(line) {
let key = &tokens[0];
let mut is_filtered = false;
if let Some(ref sec) = key.chars().next().map(|x| x.to_string()) {
if sec == "p" {
for (i, rule) in f.p.iter().enumerate() {
if !rule.is_empty() && rule != &tokens[i + 1] {
is_filtered = true;
}
}
}
if sec == "g" {
for (i, rule) in f.g.iter().enumerate() {
if !rule.is_empty() && rule != &tokens[i + 1] {
is_filtered = true;
}
}
}
if !is_filtered {
if let Some(ast_map) = m.get_mut_model().get_mut(sec) {
if let Some(ast) = ast_map.get_mut(key) {
ast.policy.insert(tokens[1..].to_vec());
}
}
}
}
is_filtered
} else {
false
}
}
mod csv {
const ESCAPE_SYMBOL: char = '\"';
const SEP: char = ',';
pub fn parse<S: AsRef<str>>(s: S) -> Option<Vec<String>> {
let s = s.as_ref().trim();
if s.is_empty() || s.starts_with('#') {
return None;
}
let mut res: Vec<String> = vec![];
let mut escape: Vec<char> = Vec::with_capacity(2);
let mut tmp: Vec<char> = vec![];
for (i, ch) in s.chars().enumerate() {
match ch {
SEP if escape.is_empty() => {
if !tmp.is_empty() {
res.push(self::join_chars(&tmp));
tmp.clear();
} else {
res.push("".to_owned());
}
if i == s.len() - 1 {
res.push("".to_owned())
}
}
ch @ ESCAPE_SYMBOL => {
if escape.is_empty() {
escape.push(ch)
} else {
escape.clear()
}
}
ch => tmp.push(ch),
}
}
if !escape.is_empty() {
panic!("unmatched escape character `{}`", ESCAPE_SYMBOL)
}
if !tmp.is_empty() {
res.push(self::join_chars(&tmp));
}
if !res.is_empty() {
Some(res)
} else {
None
}
}
fn join_chars(chars: &[char]) -> String {
chars.iter().collect::<String>().trim().to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_csv_parse_1() {
assert_eq!(
csv::parse("alice, domain1, data1, action1"),
Some(vec![
"alice".to_owned(),
"domain1".to_owned(),
"data1".to_owned(),
"action1".to_owned()
])
)
}
#[test]
fn test_csv_parse_2() {
assert_eq!(
csv::parse("alice, \"domain1, domain2\", data1 , action1"),
Some(vec![
"alice".to_owned(),
"domain1, domain2".to_owned(),
"data1".to_owned(),
"action1".to_owned()
])
)
}
#[test]
fn test_csv_parse_3() {
assert_eq!(csv::parse(","), Some(vec!["".to_owned(), "".to_owned(),]))
}
#[test]
fn test_csv_parse_4() {
assert_eq!(csv::parse(" "), None)
}
#[test]
fn test_csv_parse_5() {
assert_eq!(
csv::parse(
"alice, \"domain1, domain2\", \"data1, data2\", action1"
),
Some(vec![
"alice".to_owned(),
"domain1, domain2".to_owned(),
"data1, data2".to_owned(),
"action1".to_owned()
])
)
}
#[test]
#[should_panic]
fn test_csv_parse_6() {
assert_eq!(csv::parse("\" "), Some(vec!["\"".to_owned()]))
}
#[test]
#[should_panic]
fn test_csv_parse_7() {
assert_eq!(csv::parse("\" alice"), Some(vec!["\" alice".to_owned()]))
}
#[test]
#[should_panic]
fn test_csv_parse_8() {
assert_eq!(
csv::parse("alice, \"domain1, domain2"),
Some(vec!["alice".to_owned(), "\"domain1, domain2".to_owned(),])
)
}
#[test]
fn test_csv_parse_9() {
assert_eq!(csv::parse("\"\""), None)
}
}