From 192b4febdcec83f77fe661f827fc8bf3ca8bfd1f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:27:29 +0000 Subject: [PATCH 1/3] Initial plan From 1488b96653148d3177e5e7a40320732f9f41ea3a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:35:37 +0000 Subject: [PATCH 2/3] Initial analysis of 'in' operator bug Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- .../jcasbin/main/InOperatorBugTest.java | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java diff --git a/src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java b/src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java new file mode 100644 index 00000000..8fdef7c7 --- /dev/null +++ b/src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java @@ -0,0 +1,67 @@ +package org.casbin.jcasbin.main; + +import org.testng.annotations.Test; +import java.io.File; +import java.io.FileWriter; +import static org.testng.Assert.*; + +/** + * Test for the 'in' operator bug with tuple literals + * Issue: 'in' of matcher doesn't work: "m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')" + */ +public class InOperatorBugTest { + + @Test + public void testInOperatorWithTupleLiterals() throws Exception { + // Create temporary model and policy files + File modelFile = File.createTempFile("test_in_op_model", ".conf"); + File policyFile = File.createTempFile("test_in_op_policy", ".csv"); + modelFile.deleteOnExit(); + policyFile.deleteOnExit(); + + // Write model with matcher containing "in" operator with tuple literals + String modelText = "[request_definition]\n" + + "r = sub, obj, act\n" + + "\n" + + "[policy_definition]\n" + + "p = sub, obj, act\n" + + "\n" + + "[role_definition]\n" + + "g = _, _\n" + + "\n" + + "[policy_effect]\n" + + "e = some(where (p.eft == allow))\n" + + "\n" + + "[matchers]\n" + + "m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')\n"; + + try (FileWriter writer = new FileWriter(modelFile)) { + writer.write(modelText); + } + + // Write policy + String policyText = "p, reader, data1, read\n" + + "p, writer, data1, write\n" + + "\n" + + "g, alice, reader\n"; + + try (FileWriter writer = new FileWriter(policyFile)) { + writer.write(policyText); + } + + // Create enforcer + Enforcer e = new Enforcer(modelFile.getAbsolutePath(), policyFile.getAbsolutePath()); + + // Test 1: Should allow access because alice is a reader and wants to read data1 + assertTrue(e.enforce("alice", "data1", "read")); + + // Test 2: Should allow access because data2 is in the tuple ('data2', 'data3') + assertTrue(e.enforce("alice", "data2", "read")); + + // Test 3: Should allow access because data3 is in the tuple ('data2', 'data3') + assertTrue(e.enforce("alice", "data3", "write")); + + // Test 4: Should deny access because data4 is not in the tuple and alice is not a writer + assertFalse(e.enforce("alice", "data4", "write")); + } +} From ba5b83f2435bbe9395852136b60b2a0e81995b92 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 12 Feb 2026 14:39:49 +0000 Subject: [PATCH 3/3] Improve test coverage and address code review feedback Co-authored-by: hsluoyz <3787410+hsluoyz@users.noreply.github.com> --- .../jcasbin/main/InOperatorBugTest.java | 116 ++++++++++-------- 1 file changed, 68 insertions(+), 48 deletions(-) diff --git a/src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java b/src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java index 8fdef7c7..04742a83 100644 --- a/src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java +++ b/src/test/java/org/casbin/jcasbin/main/InOperatorBugTest.java @@ -13,55 +13,75 @@ public class InOperatorBugTest { @Test public void testInOperatorWithTupleLiterals() throws Exception { - // Create temporary model and policy files - File modelFile = File.createTempFile("test_in_op_model", ".conf"); - File policyFile = File.createTempFile("test_in_op_policy", ".csv"); - modelFile.deleteOnExit(); - policyFile.deleteOnExit(); + File modelFile = null; + File policyFile = null; - // Write model with matcher containing "in" operator with tuple literals - String modelText = "[request_definition]\n" + - "r = sub, obj, act\n" + - "\n" + - "[policy_definition]\n" + - "p = sub, obj, act\n" + - "\n" + - "[role_definition]\n" + - "g = _, _\n" + - "\n" + - "[policy_effect]\n" + - "e = some(where (p.eft == allow))\n" + - "\n" + - "[matchers]\n" + - "m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')\n"; - - try (FileWriter writer = new FileWriter(modelFile)) { - writer.write(modelText); + try { + // Create temporary model and policy files + modelFile = File.createTempFile("test_in_op_model", ".conf"); + policyFile = File.createTempFile("test_in_op_policy", ".csv"); + + // Write model with matcher containing "in" operator with tuple literals + String modelText = "[request_definition]\n" + + "r = sub, obj, act\n" + + "\n" + + "[policy_definition]\n" + + "p = sub, obj, act\n" + + "\n" + + "[role_definition]\n" + + "g = _, _\n" + + "\n" + + "[policy_effect]\n" + + "e = some(where (p.eft == allow))\n" + + "\n" + + "[matchers]\n" + + "m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act || r.obj in ('data2', 'data3')\n"; + + try (FileWriter writer = new FileWriter(modelFile)) { + writer.write(modelText); + } + + // Write policy + String policyText = "p, reader, data1, read\n" + + "p, writer, data1, write\n" + + "\n" + + "g, alice, reader\n"; + + try (FileWriter writer = new FileWriter(policyFile)) { + writer.write(policyText); + } + + // Create enforcer + Enforcer e = new Enforcer(modelFile.getAbsolutePath(), policyFile.getAbsolutePath()); + + // Test 1: Should allow access because alice is a reader and wants to read data1 + assertTrue(e.enforce("alice", "data1", "read")); + + // Test 2: Should allow access because data2 is in the tuple ('data2', 'data3') + // even though alice doesn't have explicit permission for data2 + assertTrue(e.enforce("alice", "data2", "read")); + + // Test 3: Should allow access because data3 is in the tuple ('data2', 'data3') + // The tuple check bypasses the role-based access control + assertTrue(e.enforce("alice", "data3", "write")); + + // Test 4: Should deny access because data4 is not in the tuple and alice is not a writer + assertFalse(e.enforce("alice", "data4", "write")); + + // Test 5: Even a user without any role can access data2/data3 due to tuple matching + assertTrue(e.enforce("bob", "data2", "read")); + assertTrue(e.enforce("bob", "data3", "write")); + + // Test 6: User without role cannot access data outside the tuple + assertFalse(e.enforce("bob", "data4", "write")); + } finally { + // Clean up temporary files + if (modelFile != null && modelFile.exists()) { + modelFile.delete(); + } + if (policyFile != null && policyFile.exists()) { + policyFile.delete(); + } } - - // Write policy - String policyText = "p, reader, data1, read\n" + - "p, writer, data1, write\n" + - "\n" + - "g, alice, reader\n"; - - try (FileWriter writer = new FileWriter(policyFile)) { - writer.write(policyText); - } - - // Create enforcer - Enforcer e = new Enforcer(modelFile.getAbsolutePath(), policyFile.getAbsolutePath()); - - // Test 1: Should allow access because alice is a reader and wants to read data1 - assertTrue(e.enforce("alice", "data1", "read")); - - // Test 2: Should allow access because data2 is in the tuple ('data2', 'data3') - assertTrue(e.enforce("alice", "data2", "read")); - - // Test 3: Should allow access because data3 is in the tuple ('data2', 'data3') - assertTrue(e.enforce("alice", "data3", "write")); - - // Test 4: Should deny access because data4 is not in the tuple and alice is not a writer - assertFalse(e.enforce("alice", "data4", "write")); } }