Skip to content

Commit

Permalink
feat: add string adapter (#359)
Browse files Browse the repository at this point in the history
  • Loading branch information
liulifox233 authored Jan 6, 2025
1 parent 8193b52 commit d01ccdc
Show file tree
Hide file tree
Showing 5 changed files with 359 additions and 5 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ watcher = []

[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2", features = ["js"] }
wasm-bindgen-test = "0.3.49"

[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
async-std = { version = "1.9.0", features = ["attributes"] }
Expand Down
10 changes: 7 additions & 3 deletions src/adapter/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
use async_trait::async_trait;

#[cfg(not(target_arch = "wasm32"))]
pub mod file_adapter;
pub mod memory_adapter;
pub mod null_adapter;
pub mod string_adapter;

#[cfg(not(target_arch = "wasm32"))]
pub use file_adapter::FileAdapter;
pub mod file_adapter;

pub use memory_adapter::MemoryAdapter;
pub use null_adapter::NullAdapter;
pub use string_adapter::StringAdapter;

#[cfg(not(target_arch = "wasm32"))]
pub use file_adapter::FileAdapter;

use crate::{model::Model, Result};

Expand Down
349 changes: 349 additions & 0 deletions src/adapter/string_adapter.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,349 @@
// 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());
}
}
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ pub mod prelude;

#[cfg(not(target_arch = "wasm32"))]
pub use adapter::FileAdapter;
pub use adapter::{Adapter, Filter, MemoryAdapter, NullAdapter};
pub use adapter::{Adapter, Filter, MemoryAdapter, NullAdapter, StringAdapter};

#[cfg(feature = "cached")]
pub use cache::{Cache, DefaultCache};
Expand Down
2 changes: 1 addition & 1 deletion src/prelude.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
pub use crate::{
CoreApi, DefaultModel, Enforcer, Event, EventData, EventEmitter, Filter,
IEnforcer, InternalApi, MemoryAdapter, MgmtApi, Model, NullAdapter,
RbacApi, Result, TryIntoAdapter, TryIntoModel,
RbacApi, Result, StringAdapter, TryIntoAdapter, TryIntoModel,
};

#[cfg(not(target_arch = "wasm32"))]
Expand Down

0 comments on commit d01ccdc

Please sign in to comment.