This is an automated email from the ASF dual-hosted git repository.

hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-rs.git


The following commit(s) were added to refs/heads/master by this push:
     new a6318a5  fix: stop empty policy sets from failing open
a6318a5 is described below

commit a6318a54c163673c02a4445548a19e135c68fb3a
Author: Yang Luo <[email protected]>
AuthorDate: Fri Oct 9 22:07:25 2026 +0800

    fix: stop empty policy sets from failing open
---
 src/adapter/string_adapter.rs | 70 ++++++++++++++++++++++++++++++-------------
 src/enforcer.rs               | 14 ++++++---
 src/model/default_model.rs    | 69 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 129 insertions(+), 24 deletions(-)

diff --git a/src/adapter/string_adapter.rs b/src/adapter/string_adapter.rs
index 4287fcd..168459a 100644
--- a/src/adapter/string_adapter.rs
+++ b/src/adapter/string_adapter.rs
@@ -77,30 +77,27 @@ impl Adapter for StringAdapter {
         m: &mut dyn Model,
         f: Filter<'a>,
     ) -> Result<()> {
+        self.is_filtered = false;
         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;
-                        }
-                    }
-                }
+                let ptype = &tokens[0];
+                let sec = match ptype.chars().next() {
+                    Some(c) => c.to_string(),
+                    None => continue,
+                };
+                let rule = tokens[1..].to_vec();
+                let filter = match sec.as_str() {
+                    "p" => &f.p,
+                    "g" => &f.g,
+                    _ => continue,
+                };
+                let is_filtered = filter.iter().enumerate().any(|(i, r)| {
+                    !r.is_empty() && rule.get(i).map(String::as_str) != 
Some(*r)
+                });
+
                 if !is_filtered {
-                    if let Some(ast_map) = m.get_mut_model().get_mut(sec) {
+                    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);
                         }
@@ -346,4 +343,37 @@ mod tests {
 
         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_load_filtered_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();
+
+        let filter = Filter {
+            p: vec!["alice"],
+            g: vec![],
+        };
+
+        adapter
+            .load_filtered_policy(&mut model, filter)
+            .await
+            .unwrap();
+        let enforcer = Enforcer::new(model, adapter).await.unwrap();
+
+        assert!(enforcer.enforce(("alice", "data1", "read")).unwrap());
+        assert!(!enforcer.enforce(("bob", "data2", "write")).unwrap());
+    }
 }
diff --git a/src/enforcer.rs b/src/enforcer.rs
index 40efb5e..b4f8afd 100644
--- a/src/enforcer.rs
+++ b/src/enforcer.rs
@@ -168,13 +168,16 @@ impl Enforcer {
         };
 
         if policy_len == 0 {
+            // No policy rows: bind policy tokens to unit so that only the
+            // parts of the matcher that do not depend on a policy can allow.
             for token in p_ast.tokens.iter() {
-                scope.push_constant(token, String::new());
+                scope.push_constant(token, ());
             }
 
             let eval_result = self
                 .engine
-                .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?;
+                .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)
+                .unwrap_or(false);
             let eft = if eval_result {
                 EffectKind::Allow
             } else {
@@ -313,13 +316,16 @@ impl Enforcer {
         };
 
         if policy_len == 0 {
+            // No policy rows: bind policy tokens to unit so that only the
+            // parts of the matcher that do not depend on a policy can allow.
             for token in p_ast.tokens.iter() {
-                scope.push_constant(token, String::new());
+                scope.push_constant(token, ());
             }
 
             let eval_result = self
                 .engine
-                .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)?;
+                .eval_ast_with_scope::<bool>(&mut scope, m_ast_compiled)
+                .unwrap_or(false);
             let eft = if eval_result {
                 EffectKind::Allow
             } else {
diff --git a/src/model/default_model.rs b/src/model/default_model.rs
index 90dfb06..166d526 100644
--- a/src/model/default_model.rs
+++ b/src/model/default_model.rs
@@ -554,6 +554,75 @@ mod tests {
         assert!(!e.enforce(("bob", "data2", "write")).unwrap());
     }
 
+    #[cfg(not(target_arch = "wasm32"))]
+    #[cfg_attr(
+        all(feature = "runtime-async-std", not(target_arch = "wasm32")),
+        async_std::test
+    )]
+    #[cfg_attr(
+        all(feature = "runtime-tokio", not(target_arch = "wasm32")),
+        tokio::test
+    )]
+    async fn test_no_policy_does_not_match_empty_values() {
+        let m = DefaultModel::from_str(
+            r#"
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = r.sub == p.sub && r.obj == p.obj && regexMatch(r.act, p.act)
+"#,
+        )
+        .await
+        .unwrap();
+
+        let adapter = MemoryAdapter::default();
+        let e = Enforcer::new(m, adapter).await.unwrap();
+
+        assert!(!e.enforce(("", "", "")).unwrap());
+        assert!(!e.enforce(("", "", "DELETE")).unwrap());
+    }
+
+    #[cfg(not(target_arch = "wasm32"))]
+    #[cfg_attr(
+        all(feature = "runtime-async-std", not(target_arch = "wasm32")),
+        async_std::test
+    )]
+    #[cfg_attr(
+        all(feature = "runtime-tokio", not(target_arch = "wasm32")),
+        tokio::test
+    )]
+    async fn test_no_policy_function_on_policy_value() {
+        let m = DefaultModel::from_str(
+            r#"
+[request_definition]
+r = sub, obj, act
+
+[policy_definition]
+p = sub, obj, act
+
+[policy_effect]
+e = some(where (p.eft == allow))
+
+[matchers]
+m = regexMatch(r.act, p.act) && r.sub == p.sub && r.obj == p.obj
+"#,
+        )
+        .await
+        .unwrap();
+
+        let adapter = MemoryAdapter::default();
+        let e = Enforcer::new(m, adapter).await.unwrap();
+
+        assert!(!e.enforce(("", "", "DELETE")).unwrap());
+    }
+
     #[cfg(not(target_arch = "wasm32"))]
     #[cfg_attr(
         all(feature = "runtime-async-std", not(target_arch = "wasm32")),


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to