| // Copyright 2024 The casbin Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| use crate::{ |
| error::{AdapterError, ModelError}, |
| util::parse_csv_line, |
| Adapter, Filter, Model, Result, |
| }; |
| use async_trait::async_trait; |
| use std::fmt::Write; |
| |
| #[derive(Default)] |
| pub struct StringAdapter { |
| pub(crate) policy: String, |
| is_filtered: bool, |
| } |
| |
| impl StringAdapter { |
| /// # Examples |
| /// |
| ///```rust |
| /// # use casbin::{CoreApi, DefaultModel, Enforcer, Result}; |
| /// # use casbin::prelude::*; |
| /// # #[tokio::main] |
| /// # async fn main() -> Result<()> { |
| /// # let m = DefaultModel::from_file("examples/rbac_model.conf").await?; |
| /// let a = StringAdapter::new( |
| /// r#" |
| /// p, alice, data1, read |
| /// p, bob, data2, write |
| /// p, data2_admin, data2, read |
| /// p, data2_admin, data2, write |
| /// g, alice, data2_admin |
| /// "#, |
| /// ); |
| /// let e = Enforcer::new(m, a).await?; |
| /// |
| /// assert_eq!(true, e.enforce(("alice", "data1", "read"))?); |
| /// assert_eq!(true, e.enforce(("alice", "data2", "read"))?); |
| /// assert_eq!(true, e.enforce(("bob", "data2", "write"))?); |
| /// assert_eq!(false, e.enforce(("bob", "data1", "write"))?); |
| /// # Ok(()) |
| /// # } |
| /// |
| /// ``` |
| pub fn new(s: impl ToString) -> Self { |
| Self { |
| policy: s.to_string(), |
| is_filtered: false, |
| } |
| } |
| } |
| |
| #[async_trait] |
| impl Adapter for StringAdapter { |
| async fn load_policy(&mut self, m: &mut dyn Model) -> Result<()> { |
| let policies = self.policy.split("\n"); |
| for line in policies { |
| load_policy_line(line, m); |
| } |
| Ok(()) |
| } |
| |
| async fn load_filtered_policy<'a>( |
| &mut self, |
| m: &mut dyn Model, |
| f: Filter<'a>, |
| ) -> Result<()> { |
| let policies = self.policy.split("\n"); |
| for line in policies { |
| if let Some(tokens) = parse_csv_line(line) { |
| let sec = &tokens[0]; |
| let ptype = &tokens[1]; |
| let rule = tokens[1..].to_vec().clone(); |
| let mut is_filtered = false; |
| |
| if sec == "p" { |
| for (i, r) in f.p.iter().enumerate() { |
| if !r.is_empty() && r != &rule[i + 1] { |
| is_filtered = true; |
| } |
| } |
| } |
| if sec == "g" { |
| for (i, r) in f.g.iter().enumerate() { |
| if !r.is_empty() && r != &rule[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(ptype) { |
| ast.get_mut_policy().insert(rule); |
| } |
| } |
| } else { |
| self.is_filtered = true; |
| } |
| } |
| } |
| Ok(()) |
| } |
| |
| async fn save_policy(&mut self, m: &mut dyn Model) -> Result<()> { |
| 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() { |
| writeln!(policies, "{}, {}", ptype, rule.join(", ")) |
| .map_err(|e| AdapterError(e.into()))?; |
| } |
| } |
| |
| if let Some(ast_map) = m.get_model().get("g") { |
| for (ptype, ast) in ast_map { |
| for rule in ast.get_policy() { |
| writeln!(policies, "{}, {}", ptype, rule.join(", ")) |
| .map_err(|e| AdapterError(e.into()))?; |
| } |
| } |
| } |
| |
| self.policy = policies; |
| Ok(()) |
| } |
| |
| async fn clear_policy(&mut self) -> Result<()> { |
| self.policy.clear(); |
| self.is_filtered = false; |
| Ok(()) |
| } |
| |
| async fn add_policy( |
| &mut self, |
| _sec: &str, |
| _ptype: &str, |
| _rule: Vec<String>, |
| ) -> Result<bool> { |
| // not implemented |
| Err(crate::Error::AdapterError(AdapterError(Box::new( |
| AdapterError("not implemented".to_string().into()), |
| )))) |
| } |
| |
| async fn add_policies( |
| &mut self, |
| _sec: &str, |
| _ptype: &str, |
| _rules: Vec<Vec<String>>, |
| ) -> Result<bool> { |
| // not implemented |
| Err(crate::Error::AdapterError(AdapterError(Box::new( |
| AdapterError("not implemented".to_string().into()), |
| )))) |
| } |
| |
| async fn remove_policy( |
| &mut self, |
| _sec: &str, |
| _ptype: &str, |
| _rule: Vec<String>, |
| ) -> Result<bool> { |
| // not implemented |
| Err(crate::Error::AdapterError(AdapterError(Box::new( |
| AdapterError("not implemented".to_string().into()), |
| )))) |
| } |
| |
| async fn remove_policies( |
| &mut self, |
| _sec: &str, |
| _ptype: &str, |
| _rule: Vec<Vec<String>>, |
| ) -> Result<bool> { |
| // not implemented |
| Err(crate::Error::AdapterError(AdapterError(Box::new( |
| AdapterError("not implemented".to_string().into()), |
| )))) |
| } |
| |
| async fn remove_filtered_policy( |
| &mut self, |
| _sec: &str, |
| _ptype: &str, |
| _field_index: usize, |
| _field_values: Vec<String>, |
| ) -> Result<bool> { |
| // not implemented |
| Err(crate::Error::AdapterError(AdapterError(Box::new( |
| AdapterError("not implemented".to_string().into()), |
| )))) |
| } |
| |
| fn is_filtered(&self) -> bool { |
| self.is_filtered |
| } |
| } |
| |
| fn load_policy_line(line: &str, m: &mut dyn Model) { |
| if line.is_empty() || line.starts_with('#') { |
| return; |
| } |
| |
| if let Some(tokens) = parse_csv_line(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()); |
| } |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use super::StringAdapter; |
| use crate::{Adapter, CoreApi, Filter}; |
| use crate::{DefaultModel, Enforcer}; |
| |
| #[cfg(target_arch = "wasm32")] |
| use wasm_bindgen_test::*; |
| |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-async-std"), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-tokio"), |
| tokio::test |
| )] |
| #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] |
| async fn test_load_policy() { |
| let policy = "p, alice, data1, read\np, bob, data2, write"; |
| let mut adapter = StringAdapter::new(policy); |
| let mut model = DefaultModel::from_str(include_str!( |
| "../../examples/rbac_model.conf" |
| )) |
| .await |
| .unwrap(); |
| |
| adapter.load_policy(&mut model).await.unwrap(); |
| let enforcer = Enforcer::new(model, adapter).await.unwrap(); |
| |
| assert!(enforcer.enforce(("alice", "data1", "read")).unwrap()); |
| assert!(enforcer.enforce(("bob", "data2", "write")).unwrap()); |
| assert!(!enforcer.enforce(("alice", "data2", "read")).unwrap()); |
| } |
| |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-async-std"), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-tokio"), |
| tokio::test |
| )] |
| #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] |
| async fn test_save_policy() { |
| let policy = "p, alice, data1, read\np, bob, data2, write"; |
| let mut adapter = StringAdapter::new(policy); |
| let mut model = DefaultModel::from_str(include_str!( |
| "../../examples/rbac_model.conf" |
| )) |
| .await |
| .unwrap(); |
| |
| adapter.load_policy(&mut model).await.unwrap(); |
| adapter.save_policy(&mut model).await.unwrap(); |
| |
| assert_eq!( |
| adapter.policy, |
| "p, alice, data1, read\np, bob, data2, write\n" |
| ); |
| } |
| |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-async-std"), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-tokio"), |
| tokio::test |
| )] |
| #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] |
| async fn test_clear_policy() { |
| let policy = "p, alice, data1, read\np, bob, data2, write"; |
| let mut adapter = StringAdapter::new(policy); |
| let mut model = DefaultModel::from_str(include_str!( |
| "../../examples/rbac_model.conf" |
| )) |
| .await |
| .unwrap(); |
| |
| adapter.load_policy(&mut model).await.unwrap(); |
| adapter.clear_policy().await.unwrap(); |
| |
| assert_eq!(adapter.policy, ""); |
| assert!(!adapter.is_filtered); |
| } |
| |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-async-std"), |
| async_std::test |
| )] |
| #[cfg_attr( |
| all(not(target_arch = "wasm32"), feature = "runtime-tokio"), |
| tokio::test |
| )] |
| #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] |
| async fn test_is_filtered() { |
| let policy = "p, alice, data1, read\np, bob, data2, write"; |
| let mut adapter = StringAdapter::new(policy); |
| let mut model = DefaultModel::from_str(include_str!( |
| "../../examples/rbac_model.conf" |
| )) |
| .await |
| .unwrap(); |
| |
| let filter = Filter { |
| p: vec!["alice"], |
| g: vec![], |
| }; |
| |
| adapter |
| .load_filtered_policy(&mut model, filter) |
| .await |
| .unwrap(); |
| |
| assert!(adapter.is_filtered()); |
| } |
| } |